From 835512c9dbbb7cb82e06ed3af00815c75c142824 Mon Sep 17 00:00:00 2001 From: Domenico Andreoli Date: Mon, 16 Jan 2023 16:27:56 +0100 Subject: [PATCH] Add os group solver --- .license_ignore | 2 ++ geneve/solver/__init__.py | 12 ++++++++ geneve/solver/datasets/LICENSE.txt | 4 +++ geneve/solver/datasets/README.md | 4 +++ geneve/solver/datasets/os.json | 15 +++++++++ geneve/solver/group_os.py | 49 ++++++++++++++++++++++++++++++ requirements.txt | 1 + setup.cfg | 1 + tests/reports/alerts_from_rules.md | 4 +-- tests/test_group_solvers.py | 25 +++++++++++++++ 10 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 geneve/solver/datasets/LICENSE.txt create mode 100644 geneve/solver/datasets/README.md create mode 100644 geneve/solver/datasets/os.json create mode 100644 geneve/solver/group_os.py diff --git a/.license_ignore b/.license_ignore index 51b2396d..d271b177 100644 --- a/.license_ignore +++ b/.license_ignore @@ -14,6 +14,8 @@ requirements.txt runtime.txt setup.cfg +geneve/solver/datasets/* + tests/deployment.json tests/data/config_elastic-package.yaml tests/data/config_geneve-test-env.yaml diff --git a/geneve/solver/__init__.py b/geneve/solver/__init__.py index 9ff09599..40d58f05 100644 --- a/geneve/solver/__init__.py +++ b/geneve/solver/__init__.py @@ -162,6 +162,18 @@ def solve(cls, doc, group, fields, schema, environment): solve_group = cls.solvers.get(group + ".", cls.solve_nogroup) solve_group(doc, group, fields, schema, environment) + @classmethod + def match_fields(cls, candidate, fields, schema): + for field, constraints in fields.items(): + if field not in candidate: + return False + constraints = constraints + [("==", candidate[field])] + try: + cls.solve_field({}, None, field, constraints, schema, {}) + except ConflictError: + return False + return True + def load_solvers(): from importlib import import_module diff --git a/geneve/solver/datasets/LICENSE.txt b/geneve/solver/datasets/LICENSE.txt new file mode 100644 index 00000000..0ed263fc --- /dev/null +++ b/geneve/solver/datasets/LICENSE.txt @@ -0,0 +1,4 @@ +This work is licensed under the Creative Commons Attribution-ShareAlike +4.0 International License. To view a copy of this license, visit +http://creativecommons.org/licenses/by-sa/4.0/ or send a letter to +Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. diff --git a/geneve/solver/datasets/README.md b/geneve/solver/datasets/README.md new file mode 100644 index 00000000..aed3d6e3 --- /dev/null +++ b/geneve/solver/datasets/README.md @@ -0,0 +1,4 @@ + + Creative Commons License +
+This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. diff --git a/geneve/solver/datasets/os.json b/geneve/solver/datasets/os.json new file mode 100644 index 00000000..4f409fb2 --- /dev/null +++ b/geneve/solver/datasets/os.json @@ -0,0 +1,15 @@ +[ + {"platform": "darwin", "family": "macos", "type": "macos", "name": "macOS", "version": "Catalina 10.15", "codename": "catalina", "kernel": "19.0.0"}, + {"platform": "darwin", "family": "macos", "type": "macos", "name": "macOS", "version": "Big Sur 11", "codename": "bigsur", "kernel": "20.1.0 xnu-7195.41.8~9"}, + {"platform": "darwin", "family": "macos", "type": "macos", "name": "macOS", "version": "Monterey 12", "codename": "monterey", "kernel": "21.0.1 xnu-8019.30.61~4"}, + {"platform": "darwin", "family": "macos", "type": "macos", "name": "macOS", "version": "Ventura 13", "codename": "ventura", "kernel": "22.1.0 xnu-8792.41.9~2"}, + + {"platform": "debian", "family": "debian", "type": "linux", "name": "Debian GNU/Linux", "version": "10 (buster)", "codename": "buster", "kernel": "4.19.0-21-cloud-amd64"}, + {"platform": "debian", "family": "debian", "type": "linux", "name": "Debian GNU/Linux", "version": "11 (bullseye)", "codename": "bullseye", "kernel": "5.10.0-20-cloud-amd64"}, + + {"platform": "ubuntu", "family": "debian", "type": "linux", "name": "Ubuntu", "version": "18.04 LTS (Bionic Beaver)", "codename": "bionic", "kernel": "4.15.0-20.21"}, + {"platform": "ubuntu", "family": "debian", "type": "linux", "name": "Ubuntu", "version": "20.04 LTS (Focal Fossa)", "codename": "focal", "kernel": "5.4.0-26.30"}, + {"platform": "ubuntu", "family": "debian", "type": "linux", "name": "Ubuntu", "version": "22.04 LTS (Jammy Jellyfish)", "codename": "jammy", "kernel": "5.15.0-25.25"}, + + {"platform": "windows", "family": "windows", "type": "windows", "name": "Windows Server 2019 Datacenter", "version": "10.0", "kernel": "10.0.17763.3532 (WinBuild.160101.0800)"} +] diff --git a/geneve/solver/group_os.py b/geneve/solver/group_os.py new file mode 100644 index 00000000..c85db8f3 --- /dev/null +++ b/geneve/solver/group_os.py @@ -0,0 +1,49 @@ +# Licensed to Elasticsearch B.V. under one or more contributor +# license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright +# ownership. Elasticsearch B.V. licenses this file to you under +# the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""OS group constraints solver.""" + +import random +from functools import partial +from pathlib import Path + +from faker import Faker +from faker_datasets import Provider, add_dataset + +from geneve.solver import emit_group, solver + + +@add_dataset("os", Path(__file__).parent / "datasets" / "os.json", picker="os") +class OSProvider(Provider): + pass + + +fake = Faker() +fake.add_provider(OSProvider) + + +def seed(s): + fake.seed_instance(s) + + +@solver("host.os.") +@solver("observer.os.") +@solver("user_agent.os.") +def resolve_os_group(doc, group, fields, schema, env): + match = partial(solver.match_fields, fields=fields, schema=schema) + os = fake.os(match=match) + emit_group(doc, group, os) diff --git a/requirements.txt b/requirements.txt index 9f324c08..df742f41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ build elasticsearch>=8.2.0 eql>=0.9.12 faker==15.3.4 +faker-datasets isort nbformat pytest diff --git a/setup.cfg b/setup.cfg index 8bc1107b..96a2944a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,7 @@ install_requires = elasticsearch>=8.2.0 eql>=0.9.12 faker + faker-datasets pytoml requests ruamel.yaml diff --git a/tests/reports/alerts_from_rules.md b/tests/reports/alerts_from_rules.md index dd80baa2..a49078aa 100644 --- a/tests/reports/alerts_from_rules.md +++ b/tests/reports/alerts_from_rules.md @@ -13894,8 +13894,8 @@ sequence by process.entity_id with maxspan=1m ``` ```python -[{'host': {'os': {'family': 'macos'}}, 'process': {'parent': {'executable': '/usr/sbin/installer'}, 'entity_id': 'ZFy'}, 'event': {'type': ['start'], 'category': ['process']}, '@timestamp': 0}, +[{'host': {'os': {'platform': 'darwin', 'family': 'macos', 'type': 'macos', 'name': 'macOS', 'version': 'Ventura 13', 'codename': 'ventura', 'kernel': '22.1.0 xnu-8792.41.9~2'}}, 'process': {'parent': {'executable': '/usr/sbin/installer'}, 'entity_id': 'ZFy'}, 'event': {'type': ['start'], 'category': ['process']}, '@timestamp': 0}, {'destination': {'ip': '170.121.236.89'}, 'event': {'category': ['network']}, 'process': {'entity_id': 'ZFy'}, '@timestamp': 1}, - {'host': {'os': {'family': 'macos'}}, 'process': {'parent': {'executable': '/System/Library/CoreServices/Installer.app/Contents/MacOS/Installer'}, 'entity_id': 'fUy'}, 'event': {'type': ['start'], 'category': ['process']}, '@timestamp': 2}, + {'host': {'os': {'platform': 'darwin', 'family': 'macos', 'type': 'macos', 'name': 'macOS', 'version': 'Catalina 10.15', 'codename': 'catalina', 'kernel': '19.0.0'}}, 'process': {'parent': {'executable': '/System/Library/CoreServices/Installer.app/Contents/MacOS/Installer'}, 'entity_id': 'fUy'}, 'event': {'type': ['start'], 'category': ['process']}, '@timestamp': 2}, {'destination': {'ip': '196.67.182.123'}, 'event': {'category': ['network']}, 'process': {'entity_id': 'fUy'}, '@timestamp': 3}] ``` diff --git a/tests/test_group_solvers.py b/tests/test_group_solvers.py index 51439950..0b273d47 100644 --- a/tests/test_group_solvers.py +++ b/tests/test_group_solvers.py @@ -103,3 +103,28 @@ def test_as(self): }, c.solve(schema), ) + + def test_os(self): + from geneve.solver import group_os + + group_os.seed(random.random()) + + schema = {} + c = Constraints() + c.append_constraint("host.os.") + + self.assertEqual( + { + "host": { + "os": { + "family": "windows", + "kernel": "10.0.17763.3532 (WinBuild.160101.0800)", + "name": "Windows Server 2019 Datacenter", + "platform": "windows", + "type": "windows", + "version": "10.0", + }, + }, + }, + c.solve(schema), + )