howler-sentinel-plugin 0.2.0.dev418__tar.gz → 0.2.0.dev436__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/PKG-INFO +1 -1
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/pyproject.toml +6 -6
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/mapping/sentinel_incident.py +20 -12
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/LICENSE +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/README.md +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/__init__.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/actions/azure_emit_hash.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/actions/send_to_sentinel.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/actions/update_defender_xdr_alert.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/actions/update_defender_xdr_incident.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/config.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/manifest.yml +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/mapping/xdr_alert.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/mapping/xdr_alert_evidence.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/odm/__init__.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/odm/hit.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/odm/models/sentinel.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/routes/__init__.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/routes/ingest.py +0 -0
- {howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/utils/tenant_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "howler-sentinel-plugin"
|
|
3
|
-
version = "0.2.0.
|
|
3
|
+
version = "0.2.0.dev436"
|
|
4
4
|
description = "A howler plugin for integration with Microsoft's Sentinel API"
|
|
5
5
|
authors = [{ name = "CCCS", email = "analysis-development@cyber.gc.ca" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -131,21 +131,21 @@ packages = [{ include = "sentinel" }]
|
|
|
131
131
|
|
|
132
132
|
|
|
133
133
|
[tool.poetry.group.dev.dependencies]
|
|
134
|
-
ruff = "^0.
|
|
134
|
+
ruff = "^0.16.0"
|
|
135
135
|
mypy = "^1.20.2"
|
|
136
|
-
pre-commit = "^4.6.
|
|
136
|
+
pre-commit = "^4.6.1"
|
|
137
137
|
howler-api = { path = "../../api" }
|
|
138
138
|
pytest = "^9.1.1"
|
|
139
139
|
mock = "^5.2.0"
|
|
140
140
|
pytest-cov = "^6.3.0"
|
|
141
|
-
coverage = { extras = ["toml"], version = "^7.15.
|
|
142
|
-
diff-cover = "^10.
|
|
141
|
+
coverage = { extras = ["toml"], version = "^7.15.2" }
|
|
142
|
+
diff-cover = "^10.4.1"
|
|
143
143
|
python-dotenv = "^1.2.2"
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
[tool.poetry.group.types.dependencies]
|
|
147
147
|
types-mock = "^5.2.0.20260518"
|
|
148
|
-
types-python-dateutil = "^2.9.0.
|
|
148
|
+
types-python-dateutil = "^2.9.0.20260716"
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
[tool.poetry.dependencies]
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
+
import sys
|
|
5
6
|
from typing import Any, Optional
|
|
6
7
|
|
|
7
8
|
from dateutil import parser
|
|
8
9
|
|
|
10
|
+
if sys.version_info >= (3, 11):
|
|
11
|
+
from typing import Self
|
|
12
|
+
else:
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
9
15
|
# Use standard logging for now
|
|
10
16
|
logger = logging.getLogger(__name__)
|
|
11
17
|
|
|
@@ -15,7 +21,7 @@ class SentinelIncident:
|
|
|
15
21
|
|
|
16
22
|
DEFAULT_CUSTOMER_NAME = "Unknown Customer"
|
|
17
23
|
|
|
18
|
-
def __init__(self, tid_mapping: Optional[dict[str, str]] = None):
|
|
24
|
+
def __init__(self: Self, tid_mapping: Optional[dict[str, str]] = None):
|
|
19
25
|
"""Initialize the Sentiel Incident mapper.
|
|
20
26
|
|
|
21
27
|
Args:
|
|
@@ -25,7 +31,7 @@ class SentinelIncident:
|
|
|
25
31
|
|
|
26
32
|
# --- Public mapping methods ---
|
|
27
33
|
|
|
28
|
-
def map_incident_to_bundle(self, sentinel_incident: dict[str, Any]) -> Optional[dict[str, Any]]:
|
|
34
|
+
def map_incident_to_bundle(self: Self, sentinel_incident: dict[str, Any]) -> Optional[dict[str, Any]]:
|
|
29
35
|
"""Map an Sentiel Incident to a Howler bundle.
|
|
30
36
|
|
|
31
37
|
Args:
|
|
@@ -97,8 +103,10 @@ class SentinelIncident:
|
|
|
97
103
|
}
|
|
98
104
|
self._map_graph_host_link(sentinel_incident, bundle)
|
|
99
105
|
self._map_timestamps(sentinel_incident, bundle)
|
|
100
|
-
#
|
|
101
|
-
|
|
106
|
+
# Only apply assessment for incidents that are already resolved.
|
|
107
|
+
# Otherwise, convert_hit may coerce status to resolved based on
|
|
108
|
+
# escalation inferred from assessment.
|
|
109
|
+
if classification is not None and bundle["howler"]["status"] == "resolved":
|
|
102
110
|
bundle["howler"]["assessment"] = self.map_classification(classification)
|
|
103
111
|
logger.info("Successfully mapped Sentinel Incident %s", incident_id)
|
|
104
112
|
return bundle
|
|
@@ -107,7 +115,7 @@ class SentinelIncident:
|
|
|
107
115
|
logger.error("Failed to map Sentiel Incident: %s", exc, exc_info=True)
|
|
108
116
|
return None
|
|
109
117
|
|
|
110
|
-
def get_customer_name(self, tid: str) -> str:
|
|
118
|
+
def get_customer_name(self: Self, tid: str) -> str:
|
|
111
119
|
"""Get customer name from tenant ID, return default if not found.
|
|
112
120
|
|
|
113
121
|
Args:
|
|
@@ -117,7 +125,7 @@ class SentinelIncident:
|
|
|
117
125
|
"""
|
|
118
126
|
return self.tid_mapping.get(tid, self.DEFAULT_CUSTOMER_NAME)
|
|
119
127
|
|
|
120
|
-
def map_sentinel_status_to_howler(self, sentinel_status: Optional[str]) -> str:
|
|
128
|
+
def map_sentinel_status_to_howler(self: Self, sentinel_status: Optional[str]) -> str:
|
|
121
129
|
"""Map Sentinel Incident status to Howler status.
|
|
122
130
|
|
|
123
131
|
Args:
|
|
@@ -138,7 +146,7 @@ class SentinelIncident:
|
|
|
138
146
|
}
|
|
139
147
|
return status_mapping.get(sentinel_status, "open")
|
|
140
148
|
|
|
141
|
-
def map_sentinel_user_to_howler(self, sentinel_user: Optional[str]) -> str:
|
|
149
|
+
def map_sentinel_user_to_howler(self: Self, sentinel_user: Optional[str]) -> str:
|
|
142
150
|
"""Map Sentinel user assignment to Howler format.
|
|
143
151
|
|
|
144
152
|
Args:
|
|
@@ -150,7 +158,7 @@ class SentinelIncident:
|
|
|
150
158
|
return "unassigned"
|
|
151
159
|
return sentinel_user
|
|
152
160
|
|
|
153
|
-
def map_severity_to_score(self, severity: str) -> int:
|
|
161
|
+
def map_severity_to_score(self: Self, severity: str) -> int:
|
|
154
162
|
"""Map string severity to numeric score.
|
|
155
163
|
|
|
156
164
|
Args:
|
|
@@ -161,7 +169,7 @@ class SentinelIncident:
|
|
|
161
169
|
severity_mapping: dict[str, int] = {"low": 25, "medium": 50, "high": 75, "critical": 100}
|
|
162
170
|
return severity_mapping.get(severity.lower() if severity else "medium", 50)
|
|
163
171
|
|
|
164
|
-
def map_classification(self, classification: str) -> str:
|
|
172
|
+
def map_classification(self: Self, classification: str) -> str:
|
|
165
173
|
"""Map Sentinel classification to Howler assessment.
|
|
166
174
|
|
|
167
175
|
Args:
|
|
@@ -180,7 +188,7 @@ class SentinelIncident:
|
|
|
180
188
|
|
|
181
189
|
# --- Private helper methods ---
|
|
182
190
|
|
|
183
|
-
def _map_graph_host_link(self, graph_alert: dict[str, Any], howler_hit: dict[str, Any]) -> None:
|
|
191
|
+
def _map_graph_host_link(self: Self, graph_alert: dict[str, Any], howler_hit: dict[str, Any]) -> None:
|
|
184
192
|
"""Map Graph host link from Graph alert to Howler hit.
|
|
185
193
|
|
|
186
194
|
Args:
|
|
@@ -196,7 +204,7 @@ class SentinelIncident:
|
|
|
196
204
|
howler_hit["howler"]["links"] = howler_hit["howler"].get("links", [])
|
|
197
205
|
howler_hit["howler"]["links"].append(link)
|
|
198
206
|
|
|
199
|
-
def _map_timestamps(self, graph_alert: dict[str, Any], howler_hit: dict[str, Any]) -> None:
|
|
207
|
+
def _map_timestamps(self: Self, graph_alert: dict[str, Any], howler_hit: dict[str, Any]) -> None:
|
|
200
208
|
"""Map timestamps from Graph alert to Howler hit.
|
|
201
209
|
|
|
202
210
|
Args:
|
|
@@ -222,7 +230,7 @@ class SentinelIncident:
|
|
|
222
230
|
except Exception as exc:
|
|
223
231
|
logger.warning("Invalid timestamp format for %s: %s (%s)", time_field, timestamp, exc)
|
|
224
232
|
|
|
225
|
-
def _build_labels(self, custom_tags: list[str], system_tags: list[str]) -> list[str]:
|
|
233
|
+
def _build_labels(self: Self, custom_tags: list[str], system_tags: list[str]) -> list[str]:
|
|
226
234
|
"""Build combined labels from custom and system tags.
|
|
227
235
|
|
|
228
236
|
Args:
|
|
File without changes
|
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/config.py
RENAMED
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/manifest.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/odm/__init__.py
RENAMED
|
File without changes
|
{howler_sentinel_plugin-0.2.0.dev418 → howler_sentinel_plugin-0.2.0.dev436}/sentinel/odm/hit.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|