From ae65a926a139a9ea36fa669a8dcd4c967288c09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Falconnier?= Date: Thu, 28 Nov 2024 11:53:41 +0100 Subject: [PATCH] Add event metadata routing key probe filter --- tests/core_events/test_probes.py | 42 +++++++++++++++++++ tests/core_probes/test_probe_views.py | 9 +++- zentral/core/probes/base.py | 13 ++++++ zentral/core/probes/forms.py | 12 ++++-- .../core/probes/templates/probes/probe.html | 12 +++++- 5 files changed, 81 insertions(+), 7 deletions(-) diff --git a/tests/core_events/test_probes.py b/tests/core_events/test_probes.py index 9148710f70..7cb4cc116f 100644 --- a/tests/core_events/test_probes.py +++ b/tests/core_events/test_probes.py @@ -119,6 +119,48 @@ def test_event_probes(self): self.assertEqual(list(updated_event.metadata.iter_loaded_probes()), [self.probe]) self.assertEqual(updated_event.metadata.incident_updates, expected_incident_updates) + def test_metadata_tag_filter(self): + serialized_event = { + '_zentral': { + 'created_at': '2021-02-18T20:55:00', + 'id': 'ff4db218-d5b4-4c2c-b40b-1b7fdee00dfc', + 'index': 0, + 'tags': ["daslkjdaklasdj", "a-match-haha"], + 'type': 'yolo', + }, + "yolo": "fomo", + } + event = event_from_event_d(serialized_event) + probe_source = ProbeSource.objects.create( + model="BaseProbe", + name=get_random_string(12), + status=ProbeSource.ACTIVE, + body={"filters": {"metadata": [{"event_tags": ["daslkjdaklasdj", "not-a-match"]}]}} + ) + probe = probe_source.load() + self.assertTrue(probe.test_event(event)) + + def test_routing_key_filter(self): + serialized_event = { + '_zentral': { + 'created_at': '2021-02-18T20:55:00', + 'id': 'ff4db218-d5b4-4c2c-b40b-1b7fdee00dfc', + 'index': 0, + 'routing_key': "edlkjdlqkjdqe", + 'type': 'yolo', + }, + "yolo": "fomo", + } + event = event_from_event_d(serialized_event) + probe_source = ProbeSource.objects.create( + model="BaseProbe", + name=get_random_string(12), + status=ProbeSource.ACTIVE, + body={"filters": {"metadata": [{"event_routing_keys": ["not-a-match", "edlkjdlqkjdqe"]}]}} + ) + probe = probe_source.load() + self.assertTrue(probe.test_event(event)) + def test_event_probes_with_probe_incident(self): event = event_from_event_d(serialized_event) if self.probe_with_incident.test_event(event): diff --git a/tests/core_probes/test_probe_views.py b/tests/core_probes/test_probe_views.py index 2a2f1209eb..64084d2798 100644 --- a/tests/core_probes/test_probe_views.py +++ b/tests/core_probes/test_probe_views.py @@ -79,7 +79,9 @@ def test_create_probe(self, **kwargs): response = self.client.post(reverse("probes:create"), {"name": name, "event_types": ["zentral_login", - "zentral_logout"]}, + "zentral_logout"], + "event_tags": ["heartbeat"], + "event_routing_keys": "un,deux"}, follow=True) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "probes/probe.html") @@ -91,6 +93,11 @@ def test_create_probe(self, **kwargs): self.assertEqual(probe.name, name) self.assertEqual(probe_source.name, name) self.assertEqual(probe_source.pk, probe.pk) + self.assertEqual(len(probe.metadata_filters), 1) + f = probe.metadata_filters[0] + self.assertEqual(f.event_types, {"zentral_login", "zentral_logout"}) + self.assertEqual(f.event_tags, {"heartbeat"}) + self.assertEqual(f.event_routing_keys, {"un", "deux"}) # update probe diff --git a/zentral/core/probes/base.py b/zentral/core/probes/base.py index de4fa98f05..c3785565e2 100644 --- a/zentral/core/probes/base.py +++ b/zentral/core/probes/base.py @@ -91,12 +91,18 @@ def __init__(self, data): if event_tags is None: event_tags = [] self.event_tags = set(event_tags) + event_routing_keys = data.get("event_routing_keys") + if event_routing_keys is None: + event_routing_keys = [] + self.event_routing_keys = set(event_routing_keys) def test_event_metadata(self, metadata): if self.event_types and metadata.event_type not in self.event_types: return False if self.event_tags and not metadata.all_tags & self.event_tags: return False + if self.event_routing_keys and metadata.routing_key not in self.event_routing_keys: + return False return True def get_event_type_classes(self): @@ -119,6 +125,9 @@ def get_event_tags_display(self): return ", ".join(sorted(t.replace("_", " ") for t in self.event_tags)) + def get_event_routing_keys_display(self): + return ", ".join(sorted(self.event_routing_keys)) + class MetadataFiltersSerializer(serializers.Serializer): event_types = serializers.ListField( @@ -129,6 +138,10 @@ class MetadataFiltersSerializer(serializers.Serializer): child=serializers.CharField(), required=False ) + event_routing_keys = serializers.ListField( + child=serializers.CharField(), + required=False + ) def validate(self, data): for key, val in data.items(): diff --git a/zentral/core/probes/forms.py b/zentral/core/probes/forms.py index 366e742c08..55ea843551 100644 --- a/zentral/core/probes/forms.py +++ b/zentral/core/probes/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib.postgres.forms import SimpleArrayField from django.db.models import Q from django.utils import timezone from django.utils.text import slugify @@ -96,6 +97,8 @@ class MetadataFilterForm(forms.Form): event_tags = forms.MultipleChoiceField(label="Event tags", choices=[], required=False) event_types = forms.MultipleChoiceField(label="Event types", choices=[], required=False, widget=forms.SelectMultiple(attrs={"size": 10})) + event_routing_keys = SimpleArrayField(forms.CharField(), label="Event routing keys", required=False, + help_text="Comma separated list of event routing keys.") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -110,13 +113,14 @@ def clean(self): cleaned_data = self.cleaned_data event_type = cleaned_data.get("event_types") event_tags = cleaned_data.get("event_tags") - if not event_type and not event_tags: - raise forms.ValidationError("Choose at least one event type or one tag.") + event_routing_keys = cleaned_data.get("event_routing_keys") + if not event_type and not event_tags and not event_routing_keys: + raise forms.ValidationError("Choose at least one event type or one tag or one routing key.") return cleaned_data def get_serialized_filter(self): filter_d = {} - for attr in ("event_tags", "event_types"): + for attr in ("event_tags", "event_types", "event_routing_keys"): value = self.cleaned_data.get(attr) if value: filter_d[attr] = value @@ -125,7 +129,7 @@ def get_serialized_filter(self): @staticmethod def get_initial(metadata_filter): initial_d = {} - for attr in ("event_tags", "event_types"): + for attr in ("event_tags", "event_types", "event_routing_keys"): val = getattr(metadata_filter, attr, None) if val: if isinstance(val, set): diff --git a/zentral/core/probes/templates/probes/probe.html b/zentral/core/probes/templates/probes/probe.html index c87d925977..5d6de94e91 100644 --- a/zentral/core/probes/templates/probes/probe.html +++ b/zentral/core/probes/templates/probes/probe.html @@ -217,7 +217,7 @@

Metadata

{% if metadata_filter.event_tags %} - + @@ -225,12 +225,20 @@

Metadata

{% endif %} {% if metadata_filter.event_types %} - + {% endif %} + {% if metadata_filter.event_routing_keys %} + + + + + {% endif %}
tagstag{{ metadata_filter.event_tags|length|pluralize }} {{ metadata_filter.get_event_tags_display }}
typetype{{ metadata_filter.event_types|length|pluralize }} {{ metadata_filter.get_event_types_display }}
routing key{{ metadata_filter.event_routing_keys|length|pluralize }} + {{ metadata_filter.get_event_routing_keys_display }} +
{% if perms.probes.change_probesource %}