howler-sentinel-plugin 0.2.0.dev92__py3-none-any.whl → 0.2.0.dev96__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: howler-sentinel-plugin
3
- Version: 0.2.0.dev92
3
+ Version: 0.2.0.dev96
4
4
  Summary: A howler plugin for integration with Microsoft's Sentinel API
5
5
  License: MIT
6
6
  Author: CCCS
@@ -1,7 +1,8 @@
1
1
  sentinel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sentinel/actions/ingestion.py,sha256=_t7wrmozhoT_MmktDmNxYiXrA1Q0LCiDt0rTwHBkwbc,1669
3
- sentinel/actions/send_to_sentinel.py,sha256=qxYPHqsA2jbEiQG1qJdnIVQkdvxlL1gyPXNmOUVYnmk,3280
3
+ sentinel/actions/send_to_sentinel.py,sha256=REclVHxuG0Q5PIXZ-13crQhoz8BmJJJyTc6suzpq0m0,3227
4
4
  sentinel/actions/synchronization.py,sha256=g5c34410zINWb4fSEzj94drnk5alRj_ju9xMrB39z0s,1818
5
+ sentinel/actions/update_defender_xdr_alert.py,sha256=hlCG2d2X19Zhtm1KGAqNHLG9a4yO53GZebRp--07D7c,6408
5
6
  sentinel/mapping/sentinel_incident.py,sha256=3QBnP6qFpJgE3pHvx5VvFnB3m2TVOoWxs8OysDlJVV8,9547
6
7
  sentinel/mapping/xdr_alert.py,sha256=UPoqdZsjUXmJz0dCf_qMlh9Jr0D2HcSNOFvbg8lE4wY,18250
7
8
  sentinel/mapping/xdr_alert_evidence.py,sha256=q622G4eZwFR3TCj418ZCpE83DGVicrWIQZo8Gkj_3FM,31323
@@ -10,8 +11,8 @@ sentinel/odm/models/sentinel.py,sha256=XT3XdT92uoCV5vmY9dT1jmcxRyuu9vp1gE8AwZdKB
10
11
  sentinel/routes/__init__.py,sha256=JYmKRwIfEsiPos1XuMQ2mlGDbxk6TN_cVEM0K_RNze4,130
11
12
  sentinel/routes/ingest.py,sha256=_9OdOw_9nBJseKIBnmHDLjnqZ_bDdM4wfLpLrek4-ak,7018
12
13
  sentinel/utils/tenant_utils.py,sha256=W7kBtxYNhs3vcgMf78eIRqiTpDtqjzEI2H2d0papQ_Q,1224
13
- howler_sentinel_plugin-0.2.0.dev92.dist-info/LICENSE,sha256=Wg2luVnxEkP2NSn11nh1US6W_nFFbICBAVTG9iG3t5M,1091
14
- howler_sentinel_plugin-0.2.0.dev92.dist-info/METADATA,sha256=JxB76pRL1VAh9GWtDRhdo3ls9xtATvSwzpeU7vWubzU,748
15
- howler_sentinel_plugin-0.2.0.dev92.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
16
- howler_sentinel_plugin-0.2.0.dev92.dist-info/entry_points.txt,sha256=4IJyMY0V49s3Wp659ngN_7U8g66-czeKxI-_dNAFP5g,60
17
- howler_sentinel_plugin-0.2.0.dev92.dist-info/RECORD,,
14
+ howler_sentinel_plugin-0.2.0.dev96.dist-info/LICENSE,sha256=Wg2luVnxEkP2NSn11nh1US6W_nFFbICBAVTG9iG3t5M,1091
15
+ howler_sentinel_plugin-0.2.0.dev96.dist-info/METADATA,sha256=rLIqpGiyGrs750JTWKDjpoDIesH3zh9i495TjeOwoZs,748
16
+ howler_sentinel_plugin-0.2.0.dev96.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
17
+ howler_sentinel_plugin-0.2.0.dev96.dist-info/entry_points.txt,sha256=4IJyMY0V49s3Wp659ngN_7U8g66-czeKxI-_dNAFP5g,60
18
+ howler_sentinel_plugin-0.2.0.dev96.dist-info/RECORD,,
@@ -92,7 +92,6 @@ def specification():
92
92
  "id": OPERATION_ID,
93
93
  "title": "Send hit to Microsoft Sentinel",
94
94
  "priority": 8,
95
- "i18nKey": "Send hit to Microsoft Sentinel",
96
95
  "description": {
97
96
  "short": "Send hit to Microsoft Sentinel",
98
97
  "long": execute.__doc__,
@@ -0,0 +1,184 @@
1
+ import requests
2
+ from howler.common.exceptions import HowlerRuntimeError
3
+ from howler.common.loader import datastore
4
+ from howler.common.logging import get_logger
5
+ from howler.odm.models.action import VALID_TRIGGERS
6
+ from howler.odm.models.hit import Hit
7
+ from howler.odm.models.howler_data import Assessment, HitStatus
8
+
9
+ from sentinel.utils.tenant_utils import get_token
10
+
11
+ logger = get_logger(__file__)
12
+
13
+ OPERATION_ID = "update_defender_xdr_alert"
14
+
15
+ properties_map = {
16
+ "graph": {
17
+ "status": {
18
+ HitStatus.OPEN: "new",
19
+ HitStatus.IN_PROGRESS: "inProgress",
20
+ HitStatus.ON_HOLD: "inProgress",
21
+ HitStatus.RESOLVED: "resolved",
22
+ },
23
+ "classification": {
24
+ Assessment.AMBIGUOUS: "unknown",
25
+ Assessment.SECURITY: "informationalExpectedActivity",
26
+ Assessment.DEVELOPMENT: "informationalExpectedActivity",
27
+ Assessment.FALSE_POSITIVE: "falsePositive",
28
+ Assessment.LEGITIMATE: "informationalExpectedActivity",
29
+ Assessment.TRIVIAL: "falsePositive",
30
+ Assessment.RECON: "truePositive",
31
+ Assessment.ATTEMPT: "truePositive",
32
+ Assessment.COMPROMISE: "truePositive",
33
+ Assessment.MITIGATED: "truePositive",
34
+ None: "unknown",
35
+ },
36
+ "determination": {
37
+ Assessment.AMBIGUOUS: "unknown",
38
+ Assessment.SECURITY: "securityTesting",
39
+ Assessment.DEVELOPMENT: "confirmedUserActivity",
40
+ Assessment.FALSE_POSITIVE: "other",
41
+ Assessment.LEGITIMATE: "lineOfBusinessApplication",
42
+ Assessment.TRIVIAL: "other",
43
+ Assessment.RECON: "multiStagedAttack",
44
+ Assessment.ATTEMPT: "other",
45
+ Assessment.COMPROMISE: "maliciousUserActivity",
46
+ Assessment.MITIGATED: "other",
47
+ None: "unknown",
48
+ },
49
+ },
50
+ }
51
+
52
+
53
+ def execute(query: str, **kwargs):
54
+ """Update Microsoft Defender XDR alert.
55
+
56
+ Args:
57
+ query (str): The query on which to apply this automation.
58
+ """
59
+ report = []
60
+ ds = datastore()
61
+
62
+ hits: list[Hit] = ds.hit.search(query, as_obj=True)["items"]
63
+
64
+ if not hits:
65
+ report.append(
66
+ {
67
+ "query": query,
68
+ "outcome": "error",
69
+ "title": "No hits returned by query",
70
+ "message": f"No hits returned by '{query}'",
71
+ }
72
+ )
73
+ return report
74
+
75
+ for hit in hits:
76
+ tenant_id = hit.azure.tenant_id
77
+ if not tenant_id and hit.organization.id:
78
+ tenant_id = hit.organization.id
79
+ elif not tenant_id:
80
+ report.append(
81
+ {
82
+ "query": f"howler.id:{hit.howler.id}",
83
+ "outcome": "skipped",
84
+ "title": "Azure Tenant ID is missing",
85
+ "message": "This alert does not have a set tenant ID.",
86
+ }
87
+ )
88
+ continue
89
+
90
+ try:
91
+ token = get_token(hit.azure.tenant_id)[0]
92
+ except HowlerRuntimeError as err:
93
+ logger.exception("Error on token fetching")
94
+ report.append(
95
+ {
96
+ "query": f"howler.id:{hit.howler.id}",
97
+ "outcome": "error",
98
+ "title": "Invalid Credentials",
99
+ "message": err.message,
100
+ }
101
+ )
102
+ continue
103
+
104
+ # Fetch alert details
105
+ alert_url = f"https://graph.microsoft.com/v1.0/security/alerts_v2/{hit.rule.id}"
106
+ response = requests.get(alert_url, headers={"Authorization": f"Bearer {token}"}, timeout=5.0)
107
+ if not response.ok:
108
+ logger.warning("GET request to Microsoft Graph failed with status code %s.", response.status_code)
109
+ report.append(
110
+ {
111
+ "query": query,
112
+ "outcome": "error",
113
+ "title": "Microsoft Graph API request failed",
114
+ "message": f"GET request to Microsoft Graph failed with status code {response.status_code}.",
115
+ }
116
+ )
117
+ continue
118
+
119
+ alert_data = response.json()
120
+
121
+ # Update alert
122
+ if (
123
+ "assessment" in hit.howler
124
+ and hit.howler.assessment in properties_map["graph"]["classification"]
125
+ and hit.howler.assessment in properties_map["graph"]["determination"]
126
+ ):
127
+ classification = properties_map["graph"]["classification"][hit.howler.assessment]
128
+ determination = properties_map["graph"]["determination"][hit.howler.assessment]
129
+ else:
130
+ classification = alert_data["classification"]
131
+ determination = alert_data["determination"]
132
+
133
+ status = properties_map["graph"]["status"][hit.howler.status]
134
+ assigned_to = alert_data["assignedTo"]
135
+
136
+ data = {
137
+ "assignedTo": assigned_to,
138
+ "classification": classification,
139
+ "determination": determination,
140
+ "status": status,
141
+ }
142
+
143
+ response = requests.patch(
144
+ alert_url,
145
+ json=data,
146
+ headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
147
+ timeout=5.0,
148
+ )
149
+ if not response.ok:
150
+ report.append(
151
+ {
152
+ "query": query,
153
+ "outcome": "error",
154
+ "title": "Microsoft Graph API request failed",
155
+ "message": f"PATCH request to Microsoft Graph failed with status code {response.status_code}.",
156
+ }
157
+ )
158
+
159
+ report.append(
160
+ {
161
+ "query": f"howler.id:{hit.howler.id}",
162
+ "outcome": "success",
163
+ "title": "Alert updated in XDR Defender",
164
+ "message": "Howler has successfuly propagated changes to this alert to XDR Defender.",
165
+ }
166
+ )
167
+
168
+ return report
169
+
170
+
171
+ def specification():
172
+ "Update Defender action specification"
173
+ return {
174
+ "id": OPERATION_ID,
175
+ "title": "Update Microsoft Defender XDR alert",
176
+ "priority": 8,
177
+ "description": {
178
+ "short": "Update Microsoft Defender XDR alert",
179
+ "long": execute.__doc__,
180
+ },
181
+ "roles": ["automation_basic"],
182
+ "steps": [{"args": {}, "options": {}, "validation": {}}],
183
+ "triggers": VALID_TRIGGERS,
184
+ }