From 2d11b29e457aab24647587209b5d43455020d1fe Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 8 Jan 2025 13:58:25 +0100 Subject: [PATCH 1/8] add: extend coverage, fix analyst data inspection --- src/mispguard.py | 27 ++-- ...nalyst_data_index_minimal_non-blocked.json | 17 +++ ...t_blocked_attribute_note_distribution.json | 98 ++++++++++++++ ...locked_attribute_opinion_distribution.json | 98 ++++++++++++++ ...d_attribute_relationship_distribution.json | 122 ++++++++++++++++++ ...ter_analyst_data_for_push_non-blocked.json | 7 + src/test/test_pull_scenarios.json | 14 ++ src/test/test_push_scenarios.json | 62 +++++++++ 8 files changed, 435 insertions(+), 10 deletions(-) create mode 100644 src/test/fixtures/test_analyst_data_index_minimal_non-blocked.json create mode 100644 src/test/fixtures/test_event_blocked_attribute_note_distribution.json create mode 100644 src/test/fixtures/test_event_blocked_attribute_opinion_distribution.json create mode 100644 src/test/fixtures/test_event_blocked_attribute_relationship_distribution.json create mode 100644 src/test/fixtures/test_filter_analyst_data_for_push_non-blocked.json diff --git a/src/mispguard.py b/src/mispguard.py index a159430..a884517 100644 --- a/src/mispguard.py +++ b/src/mispguard.py @@ -37,7 +37,7 @@ class MISPHTTPFlow(http.HTTPFlow): is_sighting: bool = False is_galaxy: bool = False is_analyst_data: bool = False - is_analyst_data_index: bool = False + is_analyst_data_minimal_index: bool = False is_analyst_note: bool = False is_analyst_opinion: bool = False is_analyst_relationship: bool = False @@ -236,6 +236,7 @@ def enrich_flow(self, flow: http.HTTPFlow) -> MISPHTTPFlow: if "/analyst_data/indexMinimal" in flow.request.path: flow.is_pull = True flow.is_analyst_data = True + flow.is_analyst_data_minimal_index = True if "/analyst_data/index/Note/" in flow.request.path: flow.is_pull = True @@ -255,7 +256,7 @@ def enrich_flow(self, flow: http.HTTPFlow) -> MISPHTTPFlow: if "/analyst_data/filterAnalystDataForPush" in flow.request.path: flow.is_push = True flow.is_analyst_data = True - flow.is_analyst_data_index = True + flow.is_analyst_data_minimal_index = True if "/analyst_data/pushAnalystData" in flow.request.path: flow.is_push = True @@ -322,7 +323,7 @@ def process_request(self, flow: MISPHTTPFlow) -> None: rules = self.get_rules(flow) return self.process_sightings(rules, sightings, flow) - if flow.is_push and flow.is_analyst_data and not flow.is_analyst_data_index: + if flow.is_push and flow.is_analyst_data and not flow.is_analyst_data_minimal_index: try: analyst_data = flow.request.json() logger.debug(analyst_data) @@ -394,7 +395,7 @@ def process_response(self, flow: MISPHTTPFlow) -> None: rules = self.get_rules(flow) return self.process_sightings(rules, sightings, flow) - if flow.is_pull and flow.is_analyst_data: + if flow.is_pull and flow.is_analyst_data and not flow.is_analyst_data_minimal_index: try: analyst_data = flow.response.json() except Exception as ex: @@ -521,13 +522,16 @@ def check_attribute_level_rules(self, rules: dict, attributes: dict) -> None: self.check_attribute_level_rules(rules, attribute["ShadowAttribute"]) if "Note" in attribute: - self.check_analyst_data_rules(rules, attribute["Note"]) + for note in attribute["Note"]: + self.check_analyst_data_rules(rules, note) if "Opinion" in attribute: - self.check_analyst_data_rules(rules, attribute["Opinion"]) + for opinion in attribute["Opinion"]: + self.check_analyst_data_rules(rules, opinion) if "Relationship" in attribute: - self.check_analyst_relationship_rules(rules, attribute["Relationship"]) + for relationship in attribute["Relationship"]: + self.check_analyst_relationship_rules(rules, relationship) def check_object_level_rules(self, rules: dict, objects: dict) -> None: for object in objects: @@ -540,13 +544,16 @@ def check_object_level_rules(self, rules: dict, objects: dict) -> None: self.check_attribute_level_rules(rules, object["Attribute"]) if "Note" in object: - self.check_analyst_data_rules(rules, object["Note"]) + for note in object["Note"]: + self.check_analyst_data_rules(rules, note) if "Opinion" in object: - self.check_analyst_data_rules(rules, object["Opinion"]) + for opinion in object["Opinion"]: + self.check_analyst_data_rules(rules, opinion) if "Relationship" in object: - self.check_analyst_relationship_rules(rules, object["Relationship"]) + for relationship in object["Relationship"]: + self.check_analyst_relationship_rules(rules, object["Relationship"]) def check_analyst_data_rules(self, rules: dict, analyst_data: dict) -> None: self.check_blocked_analyst_data_distribution_levels( diff --git a/src/test/fixtures/test_analyst_data_index_minimal_non-blocked.json b/src/test/fixtures/test_analyst_data_index_minimal_non-blocked.json new file mode 100644 index 0000000..36de431 --- /dev/null +++ b/src/test/fixtures/test_analyst_data_index_minimal_non-blocked.json @@ -0,0 +1,17 @@ +{ + "Note": [ + { + "5352d149-7cb8-4b91-a403-b3428c4b9dae": "2025-01-08 10:37:00" + } + ], + "Opinion": [ + { + "bc6992d6-1e38-402d-a319-b73c8de11ceb": "2025-01-08 10:37:00" + } + ], + "Relationship": [ + { + "d8990433-bf3b-47ab-8263-eb15ff2bd0d4": "2025-01-08 10:37:00" + } + ] +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_blocked_attribute_note_distribution.json b/src/test/fixtures/test_event_blocked_attribute_note_distribution.json new file mode 100644 index 0000000..78310cb --- /dev/null +++ b/src/test/fixtures/test_event_blocked_attribute_note_distribution.json @@ -0,0 +1,98 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked attribute type", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "8.8.8.8", + "Galaxy": [], + "ShadowAttribute": [], + "Note": [ + { + "id": "1", + "uuid": "9c0e3e20-b1ea-4473-81d2-845c4399c36d", + "object_uuid": "b3eedfc4-8ffa-41a2-875b-6c3d0e4602b8", + "object_type": "Attribute", + "authors": "john.doe@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-04 08:09:39", + "modified": "2024-10-04 08:09:39", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "note": "Ceci est une note", + "language": "fr-BE", + "note_type": 0, + "note_type_name": "Note", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true + } + ] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_blocked_attribute_opinion_distribution.json b/src/test/fixtures/test_event_blocked_attribute_opinion_distribution.json new file mode 100644 index 0000000..34d29b9 --- /dev/null +++ b/src/test/fixtures/test_event_blocked_attribute_opinion_distribution.json @@ -0,0 +1,98 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked attribute type", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "8.8.8.8", + "Galaxy": [], + "ShadowAttribute": [], + "Opinion": [ + { + "id": "1", + "uuid": "f43b2e9c-93c3-4d1e-a99a-e0996ced962c", + "object_uuid": "b3eedfc4-8ffa-41a2-875b-6c3d0e4602b8", + "object_type": "Event", + "authors": "john.doe@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-04 08:09:47", + "modified": "2024-10-04 08:09:47", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "opinion": "75", + "comment": "This is an opinion", + "note_type": 1, + "note_type_name": "Opinion", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true + } + ] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_blocked_attribute_relationship_distribution.json b/src/test/fixtures/test_event_blocked_attribute_relationship_distribution.json new file mode 100644 index 0000000..d03f668 --- /dev/null +++ b/src/test/fixtures/test_event_blocked_attribute_relationship_distribution.json @@ -0,0 +1,122 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked attribute type", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "8.8.8.8", + "Galaxy": [], + "ShadowAttribute": [], + "Relationship": [ + { + "id": "1", + "uuid": "41146bcb-2869-4cf0-8abb-015e8d1350c9", + "object_uuid": "a81a9424-a62d-4a3d-b402-917d48b124bd", + "object_type": "Attribute", + "authors": "admin@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-30 11:09:13", + "modified": "2024-10-30 11:09:13", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "relationship_type": "Acquaintance", + "related_object_uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "related_object_type": "Attribute", + "note_type": 2, + "note_type_name": "Relationship", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true, + "related_object": { + "Attribute": { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "2.2.2.2", + "Galaxy": [], + "ShadowAttribute": [] + } + } + } + ] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_filter_analyst_data_for_push_non-blocked.json b/src/test/fixtures/test_filter_analyst_data_for_push_non-blocked.json new file mode 100644 index 0000000..0923ddd --- /dev/null +++ b/src/test/fixtures/test_filter_analyst_data_for_push_non-blocked.json @@ -0,0 +1,7 @@ +{ + "Note": { + "5352d149-7cb8-4b91-a403-b3428c4b9dae": "2025-01-08 10:37:00", + "f8b4e5b2-6d6a-4ed5-9355-dc8666a08170": "2025-01-08 10:45:38", + "5b3cd8cf-bfea-4d4d-811b-20c16a96334a": "2025-01-08 10:49:19" + } +} \ No newline at end of file diff --git a/src/test/test_pull_scenarios.json b/src/test/test_pull_scenarios.json index 008f4ee..f8727b2 100644 --- a/src/test/test_pull_scenarios.json +++ b/src/test/test_pull_scenarios.json @@ -577,6 +577,20 @@ "expected_status_code": 200, "expected_logs": [] }, + { + "name": "analyst_data_index_minimal_non-blocked", + "host": "instance2-comp1.com", + "port": 443, + "url": "/analyst_data/indexMinimal", + "method": "POST", + "client": { + "ip": "10.0.0.1", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_analyst_data_index_minimal_non-blocked.json", + "expected_status_code": 200, + "expected_logs": [] + }, { "name": "pull_event_blocked_event_report_distribution", "host": "instance1-comp1.com", diff --git a/src/test/test_push_scenarios.json b/src/test/test_push_scenarios.json index 2fe3839..0825c28 100644 --- a/src/test/test_push_scenarios.json +++ b/src/test/test_push_scenarios.json @@ -739,6 +739,54 @@ "expected_status_code": 200, "expected_logs": [] }, + { + "name": "push_new_event_blocked_attribute_note_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_blocked_attribute_note_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_attribute_opinion_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_blocked_attribute_opinion_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_attribute_relationship_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_blocked_attribute_relationship_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, { "name": "push_analyst_notes_non-blocked", "host": "instance2-comp1.com", @@ -877,6 +925,20 @@ "request blocked: [POST]/analyst_data/pushAnalystData - object with a blocked distribution level: 0" ] }, + { + "name": "filter_analyst_data_for_push_non-blocked", + "host": "instance2-comp1.com", + "port": 443, + "url": "/analyst_data/filterAnalystDataForPush", + "method": "POST", + "client": { + "ip": "10.0.0.1", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_filter_analyst_data_for_push_non-blocked.json", + "expected_status_code": 200, + "expected_logs": [] + }, { "name": "push_new_event_blocked_distribution", "host": "instance1-comp2.com", From fc4e58b7f72e6ad57958a5de81bc79df01466893 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Wed, 8 Jan 2025 14:32:49 +0100 Subject: [PATCH 2/8] add: analyst data event level --- src/mispguard.py | 9 +- .../test_event_note_blocked_distribution.json | 98 ++++++++++++++ ...st_event_opinion_blocked_distribution.json | 98 ++++++++++++++ ...ent_relationship_blocked_distribution.json | 124 ++++++++++++++++++ src/test/test_push_scenarios.json | 48 +++++++ 5 files changed, 374 insertions(+), 3 deletions(-) create mode 100644 src/test/fixtures/test_event_note_blocked_distribution.json create mode 100644 src/test/fixtures/test_event_opinion_blocked_distribution.json create mode 100644 src/test/fixtures/test_event_relationship_blocked_distribution.json diff --git a/src/mispguard.py b/src/mispguard.py index a884517..d9e7e0b 100644 --- a/src/mispguard.py +++ b/src/mispguard.py @@ -492,13 +492,16 @@ def check_event_level_rules(self, rules: dict, event: dict) -> None: self.check_event_sharing_groups_rules(rules, event) if "Note" in event["Event"]: - self.check_analyst_data_rules(rules, event["Event"]["Note"]) + for note in event["Event"]["Note"]: + self.check_analyst_data_rules(rules, note) if "Opinion" in event["Event"]: - self.check_analyst_data_rules(rules, event["Event"]["Opinion"]) + for opinion in event["Event"]["Opinion"]: + self.check_analyst_data_rules(rules, opinion) if "Relationship" in event["Event"]: - self.check_analyst_relationship_rules(rules, event["Event"]["Relationship"]) + for relationship in event["Event"]["Relationship"]: + self.check_analyst_relationship_rules(rules, relationship) def check_attribute_level_rules(self, rules: dict, attributes: dict) -> None: logger.debug("checking attribute level rules") diff --git a/src/test/fixtures/test_event_note_blocked_distribution.json b/src/test/fixtures/test_event_note_blocked_distribution.json new file mode 100644 index 0000000..cc280bc --- /dev/null +++ b/src/test/fixtures/test_event_note_blocked_distribution.json @@ -0,0 +1,98 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "non-blocked", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "2", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "8.8.8.8", + "Galaxy": [], + "ShadowAttribute": [] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [], + "Note": [ + { + "id": "1", + "uuid": "9c0e3e20-b1ea-4473-81d2-845c4399c36d", + "object_uuid": "b3eedfc4-8ffa-41a2-875b-6c3d0e4602b8", + "object_type": "Attribute", + "authors": "john.doe@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-04 08:09:39", + "modified": "2024-10-04 08:09:39", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "note": "Ceci est une note", + "language": "fr-BE", + "note_type": 0, + "note_type_name": "Note", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true + } + ] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_opinion_blocked_distribution.json b/src/test/fixtures/test_event_opinion_blocked_distribution.json new file mode 100644 index 0000000..91479c5 --- /dev/null +++ b/src/test/fixtures/test_event_opinion_blocked_distribution.json @@ -0,0 +1,98 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "non-blocked", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "2", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "8.8.8.8", + "Galaxy": [], + "ShadowAttribute": [] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [], + "Opinion": [ + { + "id": "1", + "uuid": "f43b2e9c-93c3-4d1e-a99a-e0996ced962c", + "object_uuid": "b3eedfc4-8ffa-41a2-875b-6c3d0e4602b8", + "object_type": "Event", + "authors": "john.doe@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-04 08:09:47", + "modified": "2024-10-04 08:09:47", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "opinion": "75", + "comment": "This is an opinion", + "note_type": 1, + "note_type_name": "Opinion", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true + } + ] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_relationship_blocked_distribution.json b/src/test/fixtures/test_event_relationship_blocked_distribution.json new file mode 100644 index 0000000..cd56367 --- /dev/null +++ b/src/test/fixtures/test_event_relationship_blocked_distribution.json @@ -0,0 +1,124 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "non-blocked", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "2", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "8.8.8.8", + "Galaxy": [], + "ShadowAttribute": [] + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [], + "Relationship": [ + { + "id": "1", + "uuid": "41146bcb-2869-4cf0-8abb-015e8d1350c9", + "object_uuid": "a81a9424-a62d-4a3d-b402-917d48b124bd", + "object_type": "Attribute", + "authors": "admin@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-30 11:09:13", + "modified": "2024-10-30 11:09:13", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "relationship_type": "Acquaintance", + "related_object_uuid": "bba62eca-8f51-45c9-ad90-5abaca45d6cd", + "related_object_type": "Event", + "note_type": 2, + "note_type_name": "Relationship", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true, + "related_object": { + "Event": { + "id": "37", + "org_id": "6", + "date": "2022-03-24", + "info": "Test Event", + "user_id": "138", + "uuid": "bba62eca-8f51-45c9-ad90-5abaca45d6cd", + "published": true, + "analysis": "1", + "attribute_count": "11", + "orgc_id": "2", + "timestamp": "1730278463", + "distribution": "3", + "sharing_group_id": "0", + "proposal_email_lock": false, + "locked": true, + "threat_level_id": "2", + "publish_timestamp": "1730278489", + "sighting_timestamp": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null + } + } + } + ] + } +} \ No newline at end of file diff --git a/src/test/test_push_scenarios.json b/src/test/test_push_scenarios.json index 0825c28..de9482e 100644 --- a/src/test/test_push_scenarios.json +++ b/src/test/test_push_scenarios.json @@ -739,6 +739,54 @@ "expected_status_code": 200, "expected_logs": [] }, + { + "name": "push_new_event_blocked_note_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_note_blocked_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_opinion_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_opinion_blocked_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_relationship_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_relationship_blocked_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, { "name": "push_new_event_blocked_attribute_note_distribution", "host": "instance1-comp2.com", From 66e5e75c645e77248bd9d1ab4c1f909fb69a4602 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 13 Jan 2025 11:50:25 +0100 Subject: [PATCH 3/8] fix: error parsing object analyst relationships --- src/mispguard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mispguard.py b/src/mispguard.py index d9e7e0b..c305a7f 100644 --- a/src/mispguard.py +++ b/src/mispguard.py @@ -556,7 +556,7 @@ def check_object_level_rules(self, rules: dict, objects: dict) -> None: if "Relationship" in object: for relationship in object["Relationship"]: - self.check_analyst_relationship_rules(rules, object["Relationship"]) + self.check_analyst_relationship_rules(rules, relationship) def check_analyst_data_rules(self, rules: dict, analyst_data: dict) -> None: self.check_blocked_analyst_data_distribution_levels( From 058593363b0e4b558b9d796255c0891016a35304 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Mon, 13 Jan 2025 11:50:54 +0100 Subject: [PATCH 4/8] add: push object level analyst notes test cases --- ...te_analyst_relationship_distribution.json} | 0 ...ect_analyst_relationship_distribution.json | 143 ++++++++++++++++++ ...vent_blocked_object_note_distribution.json | 119 +++++++++++++++ ...t_blocked_object_opinion_distribution.json | 119 +++++++++++++++ src/test/test_push_scenarios.json | 52 ++++++- 5 files changed, 431 insertions(+), 2 deletions(-) rename src/test/fixtures/{test_event_blocked_attribute_relationship_distribution.json => test_event_blocked_attribute_analyst_relationship_distribution.json} (100%) create mode 100644 src/test/fixtures/test_event_blocked_object_analyst_relationship_distribution.json create mode 100644 src/test/fixtures/test_event_blocked_object_note_distribution.json create mode 100644 src/test/fixtures/test_event_blocked_object_opinion_distribution.json diff --git a/src/test/fixtures/test_event_blocked_attribute_relationship_distribution.json b/src/test/fixtures/test_event_blocked_attribute_analyst_relationship_distribution.json similarity index 100% rename from src/test/fixtures/test_event_blocked_attribute_relationship_distribution.json rename to src/test/fixtures/test_event_blocked_attribute_analyst_relationship_distribution.json diff --git a/src/test/fixtures/test_event_blocked_object_analyst_relationship_distribution.json b/src/test/fixtures/test_event_blocked_object_analyst_relationship_distribution.json new file mode 100644 index 0000000..959fe7a --- /dev/null +++ b/src/test/fixtures/test_event_blocked_object_analyst_relationship_distribution.json @@ -0,0 +1,143 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked attribute type", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [ + { + "id": "1", + "name": "domain-ip", + "meta-category": "network", + "description": "A domain/hostname and IP address seen as a tuple in a specific time frame.", + "template_uuid": "43b3b146-77eb-4931-b4cc-b66c60f28734", + "template_version": "10", + "event_id": "1", + "uuid": "feda50b8-c69c-4f5f-ae34-91b289e46799", + "timestamp": "1661956788", + "distribution": "1", + "sharing_group_id": "0", + "comment": "", + "deleted": false, + "first_seen": null, + "last_seen": null, + "ObjectReference": [], + "Attribute": [ + { + "id": "1", + "type": "hostname", + "category": "Network activity", + "to_ids": true, + "uuid": "856a3eed-aa55-4c73-bd1c-1ceadca5ca76", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956788", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "3", + "object_relation": "hostname", + "first_seen": null, + "last_seen": null, + "value": "example.com", + "Galaxy": [], + "ShadowAttribute": [], + "Tag": [] + } + ], + "Relationship": [ + { + "id": "1", + "uuid": "41146bcb-2869-4cf0-8abb-015e8d1350c9", + "object_uuid": "a81a9424-a62d-4a3d-b402-917d48b124bd", + "object_type": "Attribute", + "authors": "admin@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-30 11:09:13", + "modified": "2024-10-30 11:09:13", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "relationship_type": "Acquaintance", + "related_object_uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "related_object_type": "Attribute", + "note_type": 2, + "note_type_name": "Relationship", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true, + "related_object": { + "Attribute": { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "2.2.2.2", + "Galaxy": [], + "ShadowAttribute": [] + } + } + } + ] + } + ], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_blocked_object_note_distribution.json b/src/test/fixtures/test_event_blocked_object_note_distribution.json new file mode 100644 index 0000000..2026ed7 --- /dev/null +++ b/src/test/fixtures/test_event_blocked_object_note_distribution.json @@ -0,0 +1,119 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked attribute type", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [ + { + "id": "1", + "name": "domain-ip", + "meta-category": "network", + "description": "A domain/hostname and IP address seen as a tuple in a specific time frame.", + "template_uuid": "43b3b146-77eb-4931-b4cc-b66c60f28734", + "template_version": "10", + "event_id": "1", + "uuid": "feda50b8-c69c-4f5f-ae34-91b289e46799", + "timestamp": "1661956788", + "distribution": "1", + "sharing_group_id": "0", + "comment": "", + "deleted": false, + "first_seen": null, + "last_seen": null, + "ObjectReference": [], + "Attribute": [ + { + "id": "1", + "type": "hostname", + "category": "Network activity", + "to_ids": true, + "uuid": "856a3eed-aa55-4c73-bd1c-1ceadca5ca76", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956788", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "3", + "object_relation": "hostname", + "first_seen": null, + "last_seen": null, + "value": "example.com", + "Galaxy": [], + "ShadowAttribute": [], + "Tag": [] + } + ], + "Note": [ + { + "id": "1", + "uuid": "9c0e3e20-b1ea-4473-81d2-845c4399c36d", + "object_uuid": "b3eedfc4-8ffa-41a2-875b-6c3d0e4602b8", + "object_type": "Attribute", + "authors": "john.doe@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-04 08:09:39", + "modified": "2024-10-04 08:09:39", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "note": "Ceci est une note", + "language": "fr-BE", + "note_type": 0, + "note_type_name": "Note", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true + } + ] + } + ], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_blocked_object_opinion_distribution.json b/src/test/fixtures/test_event_blocked_object_opinion_distribution.json new file mode 100644 index 0000000..470ec0d --- /dev/null +++ b/src/test/fixtures/test_event_blocked_object_opinion_distribution.json @@ -0,0 +1,119 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked attribute type", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "1", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Attribute": [], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [ + { + "id": "1", + "name": "domain-ip", + "meta-category": "network", + "description": "A domain/hostname and IP address seen as a tuple in a specific time frame.", + "template_uuid": "43b3b146-77eb-4931-b4cc-b66c60f28734", + "template_version": "10", + "event_id": "1", + "uuid": "feda50b8-c69c-4f5f-ae34-91b289e46799", + "timestamp": "1661956788", + "distribution": "1", + "sharing_group_id": "0", + "comment": "", + "deleted": false, + "first_seen": null, + "last_seen": null, + "ObjectReference": [], + "Attribute": [ + { + "id": "1", + "type": "hostname", + "category": "Network activity", + "to_ids": true, + "uuid": "856a3eed-aa55-4c73-bd1c-1ceadca5ca76", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956788", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "3", + "object_relation": "hostname", + "first_seen": null, + "last_seen": null, + "value": "example.com", + "Galaxy": [], + "ShadowAttribute": [], + "Tag": [] + } + ], + "Opinion": [ + { + "id": "1", + "uuid": "f43b2e9c-93c3-4d1e-a99a-e0996ced962c", + "object_uuid": "b3eedfc4-8ffa-41a2-875b-6c3d0e4602b8", + "object_type": "Event", + "authors": "john.doe@admin.test", + "org_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "orgc_uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "created": "2024-10-04 08:09:47", + "modified": "2024-10-04 08:09:47", + "distribution": "0", + "sharing_group_id": null, + "locked": false, + "opinion": "75", + "comment": "This is an opinion", + "note_type": 1, + "note_type_name": "Opinion", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "10c8f445-888b-4a2d-bac8-4e1e8861d595", + "local": true + }, + "_canEdit": true + } + ] + } + ], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/test_push_scenarios.json b/src/test/test_push_scenarios.json index de9482e..9073496 100644 --- a/src/test/test_push_scenarios.json +++ b/src/test/test_push_scenarios.json @@ -820,7 +820,7 @@ ] }, { - "name": "push_new_event_blocked_attribute_relationship_distribution", + "name": "push_new_event_blocked_attribute_analyst_relationship_distribution", "host": "instance1-comp2.com", "port": 443, "url": "/events/add/metadata:1", @@ -829,7 +829,55 @@ "ip": "20.0.0.2", "port": 22 }, - "fixture_file": "./test/fixtures/test_event_blocked_attribute_relationship_distribution.json", + "fixture_file": "./test/fixtures/test_event_blocked_attribute_analyst_relationship_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_object_note_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_blocked_object_note_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_object_opinion_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_blocked_object_opinion_distribution.json", + "expected_status_code": 403, + "expected_logs": [ + "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" + ] + }, + { + "name": "push_new_event_blocked_object_analyst_relationship_distribution", + "host": "instance1-comp2.com", + "port": 443, + "url": "/events/add/metadata:1", + "method": "POST", + "client": { + "ip": "20.0.0.2", + "port": 22 + }, + "fixture_file": "./test/fixtures/test_event_blocked_object_analyst_relationship_distribution.json", "expected_status_code": 403, "expected_logs": [ "request blocked: [POST]/events/add/metadata:1 - analyst data has blocked distribution level: 0" From 530f088531670e800e7e46f4a4e4725f73c9a67a Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 14 Jan 2025 08:37:28 +0100 Subject: [PATCH 5/8] add: cover more X-UserOrgUUID filter scenarios --- ...guuid-attribute_blocked_sharing_group.json | 100 ++++++++++++++ ...nt_xuserorguuid-blocked_sharing_group.json | 2 +- ...bject-attribute_blocked_sharing_group.json | 127 ++++++++++++++++++ src/test/test_misp_guard.py | 14 +- 4 files changed, 238 insertions(+), 5 deletions(-) create mode 100644 src/test/fixtures/test_event_xuserorguuid-attribute_blocked_sharing_group.json create mode 100644 src/test/fixtures/test_event_xuserorguuid-object-attribute_blocked_sharing_group.json diff --git a/src/test/fixtures/test_event_xuserorguuid-attribute_blocked_sharing_group.json b/src/test/fixtures/test_event_xuserorguuid-attribute_blocked_sharing_group.json new file mode 100644 index 0000000..354061b --- /dev/null +++ b/src/test/fixtures/test_event_xuserorguuid-attribute_blocked_sharing_group.json @@ -0,0 +1,100 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked event with X-UserOrgUUID sharing group mismatch", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "2", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "local": true + }, + "Attribute": [ + { + "id": "1", + "type": "ip-src", + "category": "Network activity", + "to_ids": false, + "uuid": "e37a6c99-c7dc-4e41-8c79-25e35c39df0a", + "event_id": "1", + "distribution": "4", + "timestamp": "1661956302", + "comment": "", + "sharing_group_id": "1", + "deleted": false, + "disable_correlation": false, + "object_id": "0", + "object_relation": null, + "first_seen": null, + "last_seen": null, + "value": "2.2.2.2", + "Galaxy": [], + "ShadowAttribute": [], + "SharingGroup": { + "id": "1", + "name": "test_sharing_group", + "releasability": "", + "description": "", + "uuid": "e5d5a2a7-d659-4022-8b59-6afa4b658fd5", + "organisation_uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "org_id": "1", + "sync_user_id": "0", + "active": true, + "created": "2022-08-31 14:41:35", + "modified": "2022-08-31 15:06:51", + "local": true, + "roaming": false, + "Organisation": { + "id": "1", + "name": "test_org", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a" + }, + "SharingGroupOrg": [ + { + "id": "10", + "sharing_group_id": "1", + "org_id": "1", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "extend": true, + "Organisation": { + "id": "1", + "name": "test_org", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a" + } + } + ], + "SharingGroupServer": [] + } + } + ], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/fixtures/test_event_xuserorguuid-blocked_sharing_group.json b/src/test/fixtures/test_event_xuserorguuid-blocked_sharing_group.json index ae18eda..4aaa20e 100644 --- a/src/test/fixtures/test_event_xuserorguuid-blocked_sharing_group.json +++ b/src/test/fixtures/test_event_xuserorguuid-blocked_sharing_group.json @@ -11,7 +11,7 @@ "attribute_count": "4", "analysis": "0", "timestamp": "1661956788", - "distribution": "2", + "distribution": "4", "proposal_email_lock": false, "locked": false, "publish_timestamp": "1661956380", diff --git a/src/test/fixtures/test_event_xuserorguuid-object-attribute_blocked_sharing_group.json b/src/test/fixtures/test_event_xuserorguuid-object-attribute_blocked_sharing_group.json new file mode 100644 index 0000000..5941b69 --- /dev/null +++ b/src/test/fixtures/test_event_xuserorguuid-object-attribute_blocked_sharing_group.json @@ -0,0 +1,127 @@ +{ + "Event": { + "id": "1", + "orgc_id": "1", + "org_id": "1", + "date": "2022-08-31", + "threat_level_id": "1", + "info": "blocked event with X-UserOrgUUID sharing group mismatch", + "published": false, + "uuid": "385283a1-b5e0-4e10-a532-dce11c365a56", + "attribute_count": "4", + "analysis": "0", + "timestamp": "1661956788", + "distribution": "2", + "proposal_email_lock": false, + "locked": false, + "publish_timestamp": "1661956380", + "sharing_group_id": "0", + "disable_correlation": false, + "extends_uuid": "", + "protected": null, + "event_creator_email": "admin@admin.test", + "Org": { + "id": "1", + "name": "HOST", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "local": true + }, + "Orgc": { + "id": "1", + "name": "HOST", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "local": true + }, + "Attribute": [], + "ShadowAttribute": [], + "RelatedEvent": [], + "Galaxy": [], + "Object": [ + { + "id": "1", + "name": "domain-ip", + "meta-category": "network", + "description": "A domain/hostname and IP address seen as a tuple in a specific time frame.", + "template_uuid": "43b3b146-77eb-4931-b4cc-b66c60f28734", + "template_version": "10", + "event_id": "1", + "uuid": "feda50b8-c69c-4f5f-ae34-91b289e46799", + "timestamp": "1661956788", + "distribution": "4", + "sharing_group_id": "1", + "comment": "", + "deleted": false, + "first_seen": null, + "last_seen": null, + "ObjectReference": [], + "SharingGroup": { + "id": "1", + "name": "test_sharing_group", + "releasability": "", + "description": "", + "uuid": "e5d5a2a7-d659-4022-8b59-6afa4b658fd5", + "organisation_uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "org_id": "1", + "sync_user_id": "0", + "active": true, + "created": "2022-08-31 14:41:35", + "modified": "2022-08-31 15:06:51", + "local": true, + "roaming": false, + "Organisation": { + "id": "1", + "name": "test_org", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a" + }, + "SharingGroupOrg": [ + { + "id": "10", + "sharing_group_id": "1", + "org_id": "1", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a", + "extend": true, + "Organisation": { + "id": "1", + "name": "test_org", + "uuid": "87c33ffe-f83c-4eb1-be09-51f767f6fd5a" + } + } + ], + "SharingGroupServer": [] + }, + "Attribute": [ + { + "id": "1", + "type": "hostname", + "category": "Network activity", + "to_ids": true, + "uuid": "856a3eed-aa55-4c73-bd1c-1ceadca5ca76", + "event_id": "1", + "distribution": "5", + "timestamp": "1661956788", + "comment": "", + "sharing_group_id": "0", + "deleted": false, + "disable_correlation": false, + "object_id": "3", + "object_relation": "hostname", + "first_seen": null, + "last_seen": null, + "value": "example.com", + "Galaxy": [], + "ShadowAttribute": [], + "Tag": [ + { + "id": "1", + "name": "tlp:red", + "colour": "#FF0000" + } + ] + } + ] + } + ], + "EventReport": [], + "CryptographicKey": [] + } +} \ No newline at end of file diff --git a/src/test/test_misp_guard.py b/src/test/test_misp_guard.py index 2d0dd93..7836239 100644 --- a/src/test/test_misp_guard.py +++ b/src/test/test_misp_guard.py @@ -406,7 +406,15 @@ async def test_rules_push(self, scenario: dict, caplog): ), f"expected log {expected_log} not found for scenario {scenario['name']}" @pytest.mark.asyncio - async def test_pull_XUserOrgUUID_mismatch(self, caplog): + @pytest.mark.parametrize( + "scenario", + [ + "test_event_xuserorguuid-blocked_sharing_group", + "test_event_xuserorguuid-attribute_blocked_sharing_group", + "test_event_xuserorguuid-object-attribute_blocked_sharing_group", + ], + ) + async def test_pull_XUserOrgUUID_mismatch(self, scenario: str, caplog): caplog.set_level("INFO") mispguard = self.load_mispguard() @@ -418,9 +426,7 @@ async def test_pull_XUserOrgUUID_mismatch(self, caplog): headers=Headers(content_type="application/json"), ) - with open( - "test/fixtures/test_event_xuserorguuid-blocked_sharing_group.json", "rb" - ) as f: + with open("test/fixtures/" + scenario + ".json", "rb") as f: fixture = f.read() event_view_resp = tutils.tresp( From 091da78efd7fd5eca7340b5a8862166a830a8bf8 Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 14 Jan 2025 09:21:30 +0100 Subject: [PATCH 6/8] fix: add required properties to config schema, add tests for config checks --- src/config.schema.json | 5 +++++ src/mispguard.py | 26 +++++++++++++++------- src/test/fixtures/test_invalid_config.json | 1 + src/test/test_misp_guard.py | 23 +++++++++++++++++++ 4 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/test/fixtures/test_invalid_config.json diff --git a/src/config.schema.json b/src/config.schema.json index a55dff0..f4cf1b5 100644 --- a/src/config.schema.json +++ b/src/config.schema.json @@ -1,6 +1,11 @@ { "$schema": "http://json-schema.org/draft-07/schema", "type": "object", + "required": [ + "allowlist", + "compartments_rules", + "instances" + ], "properties": { "allowlist": { "type": "object", diff --git a/src/mispguard.py b/src/mispguard.py index c305a7f..3002c8a 100644 --- a/src/mispguard.py +++ b/src/mispguard.py @@ -97,12 +97,6 @@ def configure(self, updated): with open("config.schema.json", "r") as file: schema = json.load(file) self.config = json.load(open(ctx.options.config)) - - # create instances_host_mapping dictionary - self.config["instances_host_mapping"] = {} - for instance_id, instance in self.config["instances"].items(): - self.config["instances_host_mapping"][instance["host"]] = instance_id - self.config["instances_host_mapping"][instance["ip"]] = instance_id validate( instance=self.config, @@ -110,6 +104,14 @@ def configure(self, updated): format_checker=Draft202012Validator.FORMAT_CHECKER, ) + # create instances_host_mapping dictionary + self.config["instances_host_mapping"] = {} + for instance_id, instance in self.config["instances"].items(): + self.config["instances_host_mapping"][ + instance["host"] + ] = instance_id + self.config["instances_host_mapping"][instance["ip"]] = instance_id + except Exception as e: logger.error("failed to load config file: %s" % str(e)) exit(1) @@ -323,7 +325,11 @@ def process_request(self, flow: MISPHTTPFlow) -> None: rules = self.get_rules(flow) return self.process_sightings(rules, sightings, flow) - if flow.is_push and flow.is_analyst_data and not flow.is_analyst_data_minimal_index: + if ( + flow.is_push + and flow.is_analyst_data + and not flow.is_analyst_data_minimal_index + ): try: analyst_data = flow.request.json() logger.debug(analyst_data) @@ -395,7 +401,11 @@ def process_response(self, flow: MISPHTTPFlow) -> None: rules = self.get_rules(flow) return self.process_sightings(rules, sightings, flow) - if flow.is_pull and flow.is_analyst_data and not flow.is_analyst_data_minimal_index: + if ( + flow.is_pull + and flow.is_analyst_data + and not flow.is_analyst_data_minimal_index + ): try: analyst_data = flow.response.json() except Exception as ex: diff --git a/src/test/fixtures/test_invalid_config.json b/src/test/fixtures/test_invalid_config.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/test/fixtures/test_invalid_config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/test/test_misp_guard.py b/src/test/test_misp_guard.py index 7836239..3bc5372 100644 --- a/src/test/test_misp_guard.py +++ b/src/test/test_misp_guard.py @@ -356,6 +356,7 @@ async def test_rules_pull(self, scenario: dict, caplog): ), f"Expected status code {scenario['expected_status_code']} but got {flow.response.status_code} for scenario {scenario['name']}" assert "MispGuard initialized" in caplog.text for expected_log in scenario["expected_logs"]: + # debug logs # print(caplog.text) assert ( expected_log in caplog.text @@ -451,3 +452,25 @@ async def test_pull_XUserOrgUUID_mismatch(self, scenario: str, caplog): in caplog.text ) assert flow.response.status_code == 403 + + def test_no_config_file(self, caplog) -> mispguard.MispGuard: + mg = mispguard.MispGuard() + caplog.set_level("INFO") + + with taddons.context(mg) as tctx: + try: + tctx.configure(mg, config="./test/not-found.json") + self.tctx = tctx + except SystemExit: + assert "failed to load config file, use: `--set config=config.json`" in caplog.text + + def test_invalid_config_file(self, caplog) -> mispguard.MispGuard: + mg = mispguard.MispGuard() + caplog.set_level("INFO") + + with taddons.context(mg) as tctx: + try: + tctx.configure(mg, config="./test/fixtures/test_invalid_config.json") + self.tctx = tctx + except SystemExit: + assert "failed to load config file: " in caplog.text \ No newline at end of file From ea9e8b8bc366ef11f57ccec760510c27c7f402dc Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 14 Jan 2025 10:18:50 +0100 Subject: [PATCH 7/8] fix: cs --- .coverage | Bin 0 -> 53248 bytes src/test/test_misp_guard.py | 7 +++++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..65c2226f51df915c45babb58d1115fdb76144cfa GIT binary patch literal 53248 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 CUXjuO literal 0 HcmV?d00001 diff --git a/src/test/test_misp_guard.py b/src/test/test_misp_guard.py index 3bc5372..2a3d110 100644 --- a/src/test/test_misp_guard.py +++ b/src/test/test_misp_guard.py @@ -462,7 +462,10 @@ def test_no_config_file(self, caplog) -> mispguard.MispGuard: tctx.configure(mg, config="./test/not-found.json") self.tctx = tctx except SystemExit: - assert "failed to load config file, use: `--set config=config.json`" in caplog.text + assert ( + "failed to load config file, use: `--set config=config.json`" + in caplog.text + ) def test_invalid_config_file(self, caplog) -> mispguard.MispGuard: mg = mispguard.MispGuard() @@ -473,4 +476,4 @@ def test_invalid_config_file(self, caplog) -> mispguard.MispGuard: tctx.configure(mg, config="./test/fixtures/test_invalid_config.json") self.tctx = tctx except SystemExit: - assert "failed to load config file: " in caplog.text \ No newline at end of file + assert "failed to load config file: " in caplog.text From 217a3a615afbab3c744c27f566cd18c9b3ac3f1f Mon Sep 17 00:00:00 2001 From: Luciano Righetti Date: Tue, 14 Jan 2025 10:19:22 +0100 Subject: [PATCH 8/8] rm: remove converage --- .coverage | Bin 53248 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .coverage diff --git a/.coverage b/.coverage deleted file mode 100644 index 65c2226f51df915c45babb58d1115fdb76144cfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53248 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 CUXjuO