check-msdefender 1.0.0__py3-none-any.whl → 1.1.1__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.
@@ -6,6 +6,7 @@ from .vulnerabilities import register_vulnerability_commands
6
6
  from .onboarding import register_onboarding_commands
7
7
  from .machines import register_machines_commands
8
8
  from .detail import register_detail_commands
9
+ from .alerts import register_alerts_commands
9
10
 
10
11
 
11
12
  def register_all_commands(main_group: Any) -> None:
@@ -15,3 +16,4 @@ def register_all_commands(main_group: Any) -> None:
15
16
  register_onboarding_commands(main_group)
16
17
  register_machines_commands(main_group)
17
18
  register_detail_commands(main_group)
19
+ register_alerts_commands(main_group)
@@ -0,0 +1,61 @@
1
+ """Alerts commands for CLI."""
2
+
3
+ import sys
4
+ import click
5
+ from typing import Optional, Any
6
+
7
+ from check_msdefender.core.auth import get_authenticator
8
+ from check_msdefender.core.config import load_config
9
+ from check_msdefender.core.defender import DefenderClient
10
+ from check_msdefender.core.nagios import NagiosPlugin
11
+ from check_msdefender.services.alerts_service import AlertsService
12
+ from ..decorators import common_options
13
+
14
+
15
+ def register_alerts_commands(main_group: Any) -> None:
16
+ """Register alerts commands with the main CLI group."""
17
+
18
+ @main_group.command("alerts")
19
+ @common_options
20
+ def alerts_cmd(
21
+ config: str,
22
+ verbose: int,
23
+ machine_id: Optional[str],
24
+ dns_name: Optional[str],
25
+ warning: Optional[float],
26
+ critical: Optional[float],
27
+ ) -> None:
28
+ """Check alerts for Microsoft Defender."""
29
+ warning = warning if warning is not None else 1
30
+ critical = critical if critical is not None else 0
31
+
32
+ try:
33
+ # Load configuration
34
+ cfg = load_config(config)
35
+
36
+ # Get authenticator
37
+ authenticator = get_authenticator(cfg)
38
+
39
+ # Create Defender client
40
+ client = DefenderClient(authenticator, verbose_level=verbose)
41
+
42
+ # Create the alerts service
43
+ service = AlertsService(client, verbose_level=verbose)
44
+
45
+ # Create Nagios plugin
46
+ plugin = NagiosPlugin(service, "alerts")
47
+
48
+ # Execute check
49
+ result = plugin.check(
50
+ machine_id=machine_id,
51
+ dns_name=dns_name,
52
+ warning=warning,
53
+ critical=critical,
54
+ verbose=verbose,
55
+ )
56
+
57
+ sys.exit(result or 0)
58
+
59
+ except Exception as e:
60
+ print(f"UNKNOWN: {str(e)}")
61
+ sys.exit(3)
@@ -167,6 +167,44 @@ class DefenderClient:
167
167
  self.logger.debug(f"Response: {str(e.response.content)}")
168
168
  raise DefenderAPIError(f"Failed to query MS Defender API: {str(e)}")
169
169
 
170
+ def get_alerts(self) -> Dict[str, Any]:
171
+ """Get alerts from Microsoft Defender."""
172
+ self.logger.method_entry("get_alerts")
173
+
174
+ token = self._get_token()
175
+
176
+ url = f"{self.base_url}/api/alerts"
177
+ headers = {
178
+ "Authorization": f"Bearer {token}",
179
+ "Content-Type": DefenderClient.application_json,
180
+ }
181
+
182
+ params = {
183
+ "$top": "100",
184
+ "$expand": "evidence",
185
+ "$orderby": "alertCreationTime desc",
186
+ "$select": "status,title,machineId,computerDnsName,alertCreationTime,firstEventTime,lastEventTime,lastUpdateTime,severity",
187
+ }
188
+
189
+ try:
190
+ start_time = time.time()
191
+ self.logger.info("Querying alerts")
192
+ response = requests.get(url, headers=headers, params=params, timeout=self.timeout)
193
+ elapsed = time.time() - start_time
194
+
195
+ self.logger.api_call("GET", url, response.status_code, elapsed)
196
+ response.raise_for_status()
197
+
198
+ result = cast(Dict[str, Any], response.json())
199
+ self.logger.json_response(str(result))
200
+ self.logger.method_exit("get_alerts", result)
201
+ return result
202
+ except requests.RequestException as e:
203
+ self.logger.debug(f"API request failed: {str(e)}")
204
+ if hasattr(e, "response") and e.response is not None:
205
+ self.logger.debug(f"Response: {str(e.response.content)}")
206
+ raise DefenderAPIError(f"Failed to query MS Defender API: {str(e)}")
207
+
170
208
  def _get_token(self) -> str:
171
209
  """Get access token from authenticator."""
172
210
  self.logger.trace("Getting access token from authenticator")
@@ -0,0 +1,98 @@
1
+ """Alerts service implementation."""
2
+
3
+ from datetime import datetime
4
+ from typing import Dict, Optional, Any, List
5
+ from check_msdefender.core.exceptions import ValidationError
6
+ from check_msdefender.core.logging_config import get_verbose_logger
7
+
8
+
9
+ class AlertsService:
10
+ """Service for checking machine alerts."""
11
+
12
+ def __init__(self, defender_client: Any, verbose_level: int = 0) -> None:
13
+ """Initialize with Defender client."""
14
+ self.defender = defender_client
15
+ self.logger = get_verbose_logger(__name__, verbose_level)
16
+
17
+ def get_result(
18
+ self, machine_id: Optional[str] = None, dns_name: Optional[str] = None
19
+ ) -> Dict[str, Any]:
20
+ """Get alerts result with value and details for a machine."""
21
+ self.logger.method_entry("get_result", machine_id=machine_id, dns_name=dns_name)
22
+
23
+ if not machine_id and not dns_name:
24
+ raise ValidationError("Either machine_id or dns_name must be provided")
25
+
26
+ # Get machine information
27
+ target_dns_name = dns_name
28
+ target_machine_id = machine_id
29
+
30
+ if machine_id:
31
+ # Get DNS name from machine_id
32
+ machine_details = self.defender.get_machine_by_id(machine_id)
33
+ target_dns_name = machine_details.get("computerDnsName", "Unknown")
34
+ elif dns_name:
35
+ # Get machine_id from dns_name
36
+ dns_response = self.defender.get_machine_by_dns_name(dns_name)
37
+ machines = dns_response.get("value", [])
38
+ if not machines:
39
+ raise ValidationError(f"Machine not found with DNS name: {dns_name}")
40
+ target_machine_id = machines[0].get("id")
41
+ target_dns_name = dns_name
42
+
43
+ # Get all alerts
44
+ self.logger.info("Fetching alerts from Microsoft Defender")
45
+ alerts_data = self.defender.get_alerts()
46
+ all_alerts = alerts_data.get("value", [])
47
+
48
+ # Filter alerts for the specific machine
49
+ machine_alerts = [
50
+ alert
51
+ for alert in all_alerts
52
+ if alert.get("machineId") == target_machine_id
53
+ or alert.get("computerDnsName") == target_dns_name
54
+ ]
55
+
56
+ self.logger.info(f"Found {len(machine_alerts)} alerts for machine {target_dns_name}")
57
+
58
+ # Categorize alerts by status and severity
59
+ unresolved_alerts = [alert for alert in machine_alerts if alert.get("status") != "Resolved"]
60
+ informational_alerts = [
61
+ alert for alert in unresolved_alerts if alert.get("severity") == "Informational"
62
+ ]
63
+ critical_warning_alerts = [
64
+ alert
65
+ for alert in unresolved_alerts
66
+ if alert.get("severity") in ["High", "Medium", "Low"]
67
+ ]
68
+
69
+ # Create details for output
70
+ details = []
71
+ if unresolved_alerts:
72
+ summary_line = f"Unresolved alerts for {target_dns_name}"
73
+ if informational_alerts and not critical_warning_alerts:
74
+ summary_line = f"Unresolved informational alerts for {target_dns_name}"
75
+ elif critical_warning_alerts:
76
+ summary_line = f"Unresolved alerts for {target_dns_name}"
77
+ details.append(summary_line)
78
+
79
+ # Add individual alerts (limit to 10)
80
+ for alert in unresolved_alerts[:10]:
81
+ creation_time = alert.get("alertCreationTime", "Unknown")
82
+ title = alert.get("title", "Unknown alert")
83
+ status = alert.get("status", "Unknown")
84
+ severity = alert.get("severity", "Unknown")
85
+ details.append(f"{creation_time} - {title} ({status} {severity.lower()})")
86
+
87
+ # Return the number of unresolved alerts as the value
88
+ # This will be used by Nagios plugin for determining status based on thresholds
89
+ value = len(unresolved_alerts)
90
+
91
+ result = {
92
+ "value": value,
93
+ "details": details,
94
+ }
95
+
96
+ self.logger.info(f"Alert analysis complete: {len(unresolved_alerts)} unresolved alerts")
97
+ self.logger.method_exit("get_result", result)
98
+ return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: check-msdefender
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: A Nagios plugin for monitoring Microsoft Defender API endpoints
5
5
  Author-email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT
@@ -46,7 +46,7 @@ A comprehensive **Nagios plugin** for monitoring Microsoft Defender for Endpoint
46
46
  ## ✨ Features
47
47
 
48
48
  - 🔐 **Dual Authentication** - Support for Client Secret and Certificate-based authentication
49
- - 🎯 **Multiple Endpoints** - Monitor onboarding status, last seen, vulnerabilities, and machine details
49
+ - 🎯 **Multiple Endpoints** - Monitor onboarding status, last seen, vulnerabilities, alerts, and machine details
50
50
  - 📊 **Nagios Compatible** - Standard exit codes and performance data output
51
51
  - 🏗️ **Clean Architecture** - Modular design with testable components
52
52
  - 🔧 **Flexible Configuration** - File-based configuration with sensible defaults
@@ -78,6 +78,9 @@ check_msdefender lastseen -d machine.domain.tld -W 7 -C 30
78
78
  # Check vulnerabilities
79
79
  check_msdefender vulnerabilities -d machine.domain.tld -W 10 -C 100
80
80
 
81
+ # Check alerts
82
+ check_msdefender alerts -d machine.domain.tld -W 1 -C 5
83
+
81
84
  # List all machines
82
85
  check_msdefender machines
83
86
 
@@ -92,6 +95,7 @@ check_msdefender detail -d machine.domain.tld
92
95
  | `onboarding` | Check machine onboarding status | W:1, C:2 |
93
96
  | `lastseen` | Days since machine last seen | W:7, C:30 |
94
97
  | `vulnerabilities` | Vulnerability score calculation | W:10, C:100 |
98
+ | `alerts` | Count of unresolved alerts | W:1, C:0 |
95
99
  | `machines` | List all machines | W:10, C:25 |
96
100
  | `detail` | Get detailed machine information | - |
97
101
 
@@ -103,6 +107,14 @@ The vulnerability score is calculated as:
103
107
  - **Medium vulnerabilities** × 5
104
108
  - **Low vulnerabilities** × 1
105
109
 
110
+ ### Alert Monitoring
111
+
112
+ The alerts command monitors unresolved security alerts for a machine:
113
+ - **Counts only unresolved alerts** (status ≠ "Resolved")
114
+ - **Excludes informational alerts** when critical/warning alerts exist
115
+ - **Shows alert details** including creation time, title, and severity
116
+ - **Default thresholds**: Warning at 1 alert, Critical at 0 (meaning any alert triggers warning)
117
+
106
118
  ### Onboarding Status Values
107
119
 
108
120
  - `0` - Onboarded ✅
@@ -145,6 +157,7 @@ timeout = 5
145
157
  - `Machine.Read.All`
146
158
  - `Vulnerability.Read`
147
159
  - `Vulnerability.Read.All`
160
+ - `Alert.Read.All`
148
161
  3. **Create Authentication** (Secret or Certificate)
149
162
  4. **Note Credentials** (Client ID, Tenant ID, Secret/Certificate)
150
163
 
@@ -182,6 +195,11 @@ define command {
182
195
  command_name check_defender_vulnerabilities
183
196
  command_line $USER1$/check_msdefender/bin/check_msdefender vulnerabilities -d $HOSTALIAS$ -W 10 -C 100
184
197
  }
198
+
199
+ define command {
200
+ command_name check_defender_alerts
201
+ command_line $USER1$/check_msdefender/bin/check_msdefender alerts -d $HOSTALIAS$ -W 1 -C 5
202
+ }
185
203
  ```
186
204
 
187
205
  ### Service Definitions
@@ -208,6 +226,13 @@ define service {
208
226
  check_command check_defender_vulnerabilities
209
227
  hostgroup_name msdefender
210
228
  }
229
+
230
+ define service {
231
+ use generic-service
232
+ service_description DEFENDER_ALERTS
233
+ check_command check_defender_alerts
234
+ hostgroup_name msdefender
235
+ }
211
236
  ```
212
237
 
213
238
  ## 🏗️ Architecture
@@ -221,6 +246,7 @@ check_msdefender/
221
246
  │ │ ├── onboarding.py # Onboarding status command
222
247
  │ │ ├── lastseen.py # Last seen command
223
248
  │ │ ├── vulnerabilities.py # Vulnerabilities command
249
+ │ │ ├── alerts.py # Alerts monitoring command
224
250
  │ │ ├── machines.py # List machines command
225
251
  │ │ └── detail.py # Machine detail command
226
252
  │ ├── decorators.py # Common CLI decorators
@@ -236,6 +262,7 @@ check_msdefender/
236
262
  │ ├── onboarding_service.py # Onboarding business logic
237
263
  │ ├── lastseen_service.py # Last seen business logic
238
264
  │ ├── vulnerabilities_service.py # Vulnerability business logic
265
+ │ ├── alerts_service.py # Alerts monitoring business logic
239
266
  │ ├── machines_service.py # Machines business logic
240
267
  │ ├── detail_service.py # Detail business logic
241
268
  │ └── models.py # Data models
@@ -317,6 +344,14 @@ DEFENDER WARNING - Last seen: 10 days ago | lastseen=10;7;30;0;
317
344
  DEFENDER CRITICAL - Vulnerability score: 150 (1 Critical, 5 High) | vulnerabilities=150;10;100;0;
318
345
  ```
319
346
 
347
+ ### Alerts Warning
348
+ ```
349
+ DEFENDER WARNING - Unresolved alerts for machine.domain.com | alerts=2;1;5;0;
350
+ Unresolved alerts for machine.domain.com
351
+ 2025-09-14T10:22:14.12Z - Suspicious activity detected (New high)
352
+ 2025-09-14T12:00:00.00Z - Malware detection (InProgress medium)
353
+ ```
354
+
320
355
  ## 🔧 Troubleshooting
321
356
 
322
357
  ### Common Issues
@@ -5,7 +5,8 @@ check_msdefender/cli/__init__.py,sha256=NWaS5ZI9_252AcReugF_WGPMOvQ_B7sC_s3pSrGu
5
5
  check_msdefender/cli/__main__.py,sha256=TuNsRSdnkQm9OdBTAwD5aB2zV_Irc50WgylVWhrfnLY,124
6
6
  check_msdefender/cli/decorators.py,sha256=iMd2zrQI2SVSTa9hD8w4AjFqsctFaWUIzdT8_cSTVG8,772
7
7
  check_msdefender/cli/handlers.py,sha256=RAUsH8gI_fQV4ZNvs3Ih1lCoSOSortpVWRhyQHXe6yU,1418
8
- check_msdefender/cli/commands/__init__.py,sha256=mKF0lp4uMI9VYQGFg7N4W74GOLmACeXWVxpReLvre34,644
8
+ check_msdefender/cli/commands/__init__.py,sha256=VkPrCVpBK3DnhVDZuwd_4uK69JYAE9rHZBjAZhyOBII,730
9
+ check_msdefender/cli/commands/alerts.py,sha256=R3Xb_X7fjc6zNQ0mYQ01DW_pIxIHu_NctbKjrZu8BH0,1834
9
10
  check_msdefender/cli/commands/detail.py,sha256=k2pDCGXtiGbKmXxDYUvkmUgmQA60jKdIzTY-Jt160jE,2532
10
11
  check_msdefender/cli/commands/lastseen.py,sha256=TJHArqLkQtXF1XL5U5Dk_ExfVhK95C2TJQg0WBHbyp8,1891
11
12
  check_msdefender/cli/commands/machines.py,sha256=uHpxc2j4iJnbhp2MRAW-KR1K0NPrqdqQtJbGgSRO5tk,1737
@@ -14,20 +15,21 @@ check_msdefender/cli/commands/vulnerabilities.py,sha256=EzllIUw67qAnarKVTy-zx4nW
14
15
  check_msdefender/core/__init__.py,sha256=naBiEkixiWTuHU3GENk8fqC8H3p_hkzRsmSY2uiM_TQ,47
15
16
  check_msdefender/core/auth.py,sha256=7mkGmhGHy4t38O0e4Rz7dQ52xfMbK3IUXMlw3u83aB4,1585
16
17
  check_msdefender/core/config.py,sha256=IoWBL_DB110F4i6hFfli6iFDBXx57dHh32lCuLkcgNk,1170
17
- check_msdefender/core/defender.py,sha256=5_Mq1p9WxlttQeeqnY9RCWgDbf2Wbagw4LUFY2aqrrA,7315
18
+ check_msdefender/core/defender.py,sha256=XicGQrWuc9UCBr_Vc0SZxNSoZ_OwFXiFwfP_c9-eRtc,8838
18
19
  check_msdefender/core/exceptions.py,sha256=X4s_XM64SEVSs-4mGKqnF8xXwGFY3E0buvkgRNuCCX4,600
19
20
  check_msdefender/core/logging_config.py,sha256=27gLjvbP_AgedDQWZQEFfn_CGn5y6HcJQlI5jlxQHow,4067
20
21
  check_msdefender/core/nagios.py,sha256=nZSo-1VV57WFSieyRp456tw_OqpjXOoM_MEjnLkgxlE,6600
21
22
  check_msdefender/services/__init__.py,sha256=_fiKXxcz263IghXn9BnUWDKPgedhUPoSakEN3tBd2SU,44
23
+ check_msdefender/services/alerts_service.py,sha256=RNdjsmL9YPjPl4Dv98Z9ceWhIt7k9g4lwIGpJTk9BUo,4069
22
24
  check_msdefender/services/detail_service.py,sha256=i-jXubNfsNf-fS6ba2MQecN886GzU0UC40DwS3HrnnY,3382
23
25
  check_msdefender/services/lastseen_service.py,sha256=LiNVeUbAoMzowMvE90P7zCtKFHBLbIDp5mmkVHRLwqs,3128
24
26
  check_msdefender/services/machines_service.py,sha256=5s5BXB4GUMQ8z3rPy32lybp0DslG0QVhWxm-n_AU97k,3119
25
27
  check_msdefender/services/models.py,sha256=8p8UHh86h9TjeYahhu_qCBpfuGGS3tObhtlpYk9kB8I,985
26
28
  check_msdefender/services/onboarding_service.py,sha256=RIOsvALCoKV0YqnCHKYRkelSPrO-F-6vNBLlto4MpiI,2686
27
29
  check_msdefender/services/vulnerabilities_service.py,sha256=ikD6E-hg7LtvCiTg7cTCqGSTly6Wgtql82NJD81D2n0,6812
28
- check_msdefender-1.0.0.dist-info/licenses/LICENSE,sha256=kW3DwIsKc9HVYdS4f4tI6sLo-EPqBQbz-WmuvHU4Nak,1065
29
- check_msdefender-1.0.0.dist-info/METADATA,sha256=feCju74gUaR5YTuoshCDVnYagetTZWpFt64Itep5ckw,12750
30
- check_msdefender-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- check_msdefender-1.0.0.dist-info/entry_points.txt,sha256=EMA_qKSvf5dC6yRrajd0W-UgS3C5Ce0o04i3_5A34Cs,63
32
- check_msdefender-1.0.0.dist-info/top_level.txt,sha256=0XgjD7gBWFImxE44zghS94ZGdonRZlfVEpfspnBnG5A,17
33
- check_msdefender-1.0.0.dist-info/RECORD,,
30
+ check_msdefender-1.1.1.dist-info/licenses/LICENSE,sha256=kW3DwIsKc9HVYdS4f4tI6sLo-EPqBQbz-WmuvHU4Nak,1065
31
+ check_msdefender-1.1.1.dist-info/METADATA,sha256=Ew5sloXo0tys9KMjetkTtsu35oPa2MYuqL3g0fPFkPw,14074
32
+ check_msdefender-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ check_msdefender-1.1.1.dist-info/entry_points.txt,sha256=EMA_qKSvf5dC6yRrajd0W-UgS3C5Ce0o04i3_5A34Cs,63
34
+ check_msdefender-1.1.1.dist-info/top_level.txt,sha256=0XgjD7gBWFImxE44zghS94ZGdonRZlfVEpfspnBnG5A,17
35
+ check_msdefender-1.1.1.dist-info/RECORD,,