souleyez 2.32.0__py3-none-any.whl → 2.35.0__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.
souleyez/__init__.py CHANGED
@@ -1 +1,2 @@
1
- __version__ = '2.32.0'
1
+ __version__ = '2.35.0'
2
+
souleyez/docs/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SoulEyez Documentation
2
2
 
3
- **Version:** 2.32.0
3
+ **Version:** 2.35.0
4
4
  **Last Updated:** January 9, 2026
5
5
  **Organization:** CyberSoul Security
6
6
 
@@ -30,6 +30,7 @@ from souleyez.integrations.siem.wazuh import WazuhSIEMClient
30
30
  from souleyez.integrations.siem.splunk import SplunkSIEMClient
31
31
  from souleyez.integrations.siem.elastic import ElasticSIEMClient
32
32
  from souleyez.integrations.siem.sentinel import SentinelSIEMClient
33
+ from souleyez.integrations.siem.googlesecops import GoogleSecOpsSIEMClient
33
34
  from souleyez.integrations.siem.factory import SIEMFactory
34
35
 
35
36
  __all__ = [
@@ -45,4 +46,5 @@ __all__ = [
45
46
  'SplunkSIEMClient',
46
47
  'ElasticSIEMClient',
47
48
  'SentinelSIEMClient',
49
+ 'GoogleSecOpsSIEMClient',
48
50
  ]
@@ -11,7 +11,8 @@ from souleyez.integrations.siem.base import SIEMClient, SIEMConnectionStatus
11
11
 
12
12
 
13
13
  # Registry of available SIEM types
14
- SIEM_TYPES = ['wazuh', 'splunk', 'elastic', 'sentinel']
14
+ # Ordered: Open Source first, then Commercial
15
+ SIEM_TYPES = ['wazuh', 'elastic', 'splunk', 'sentinel', 'google_secops']
15
16
 
16
17
 
17
18
  class SIEMFactory:
@@ -60,6 +61,10 @@ class SIEMFactory:
60
61
  from souleyez.integrations.siem.sentinel import SentinelSIEMClient
61
62
  return SentinelSIEMClient.from_config(config)
62
63
 
64
+ elif siem_type_lower == 'google_secops':
65
+ from souleyez.integrations.siem.googlesecops import GoogleSecOpsSIEMClient
66
+ return GoogleSecOpsSIEMClient.from_config(config)
67
+
63
68
  else:
64
69
  raise ValueError(
65
70
  f"Unsupported SIEM type: {siem_type}. "
@@ -114,7 +119,7 @@ class SIEMFactory:
114
119
  info_map = {
115
120
  'wazuh': {
116
121
  'name': 'Wazuh',
117
- 'description': 'Open source security monitoring (OSSEC fork)',
122
+ 'description': '[Open Source] Security monitoring platform (OSSEC fork)',
118
123
  'config_fields': [
119
124
  {'name': 'api_url', 'label': 'Manager API URL', 'required': True,
120
125
  'placeholder': 'https://wazuh.example.com:55000'},
@@ -130,7 +135,7 @@ class SIEMFactory:
130
135
  },
131
136
  'splunk': {
132
137
  'name': 'Splunk',
133
- 'description': 'Enterprise SIEM and log management platform',
138
+ 'description': '[Commercial] Enterprise SIEM and log management',
134
139
  'config_fields': [
135
140
  {'name': 'api_url', 'label': 'REST API URL', 'required': True,
136
141
  'placeholder': 'https://splunk.example.com:8089'},
@@ -144,7 +149,7 @@ class SIEMFactory:
144
149
  },
145
150
  'elastic': {
146
151
  'name': 'Elastic Security',
147
- 'description': 'Elastic SIEM (formerly Elastic Security)',
152
+ 'description': '[Open Source] Elastic Stack security solution (ELK SIEM)',
148
153
  'config_fields': [
149
154
  {'name': 'elasticsearch_url', 'label': 'Elasticsearch URL', 'required': True,
150
155
  'placeholder': 'https://elastic.example.com:9200'},
@@ -159,7 +164,7 @@ class SIEMFactory:
159
164
  },
160
165
  'sentinel': {
161
166
  'name': 'Microsoft Sentinel',
162
- 'description': 'Azure cloud-native SIEM',
167
+ 'description': '[Commercial] Azure cloud-native SIEM',
163
168
  'config_fields': [
164
169
  {'name': 'tenant_id', 'label': 'Azure Tenant ID', 'required': True},
165
170
  {'name': 'client_id', 'label': 'App Client ID', 'required': True},
@@ -170,6 +175,22 @@ class SIEMFactory:
170
175
  {'name': 'workspace_id', 'label': 'Workspace ID (GUID)', 'required': True},
171
176
  ],
172
177
  },
178
+ 'google_secops': {
179
+ 'name': 'Google SecOps',
180
+ 'description': '[Commercial] Google Cloud security operations (Chronicle)',
181
+ 'config_fields': [
182
+ {'name': 'customer_id', 'label': 'Chronicle Customer ID', 'required': True,
183
+ 'placeholder': 'Your Chronicle customer ID'},
184
+ {'name': 'region', 'label': 'Chronicle Region', 'required': True,
185
+ 'placeholder': 'us, europe, asia-southeast1'},
186
+ {'name': 'project_id', 'label': 'Google Cloud Project ID', 'required': False,
187
+ 'placeholder': 'Optional if in service account JSON'},
188
+ {'name': 'credentials_json', 'label': 'Service Account JSON', 'required': True,
189
+ 'secret': True, 'type': 'textarea',
190
+ 'placeholder': 'Paste service account JSON key'},
191
+ {'name': 'verify_ssl', 'label': 'Verify SSL', 'required': False, 'type': 'boolean'},
192
+ ],
193
+ },
173
194
  }
174
195
 
175
196
  return info_map.get(siem_type.lower(), {
@@ -0,0 +1,614 @@
1
+ """
2
+ Google SecOps (Chronicle) SIEM Client.
3
+
4
+ Implements the SIEMClient interface for Google SecOps (formerly Chronicle SIEM).
5
+ Uses Chronicle REST APIs for querying detections, events, and rules.
6
+ """
7
+
8
+ import base64
9
+ import json
10
+ import time
11
+ from datetime import datetime, timedelta
12
+ from typing import Dict, List, Optional, Any
13
+
14
+ import requests
15
+
16
+ from souleyez.integrations.siem.base import (
17
+ SIEMClient,
18
+ SIEMAlert,
19
+ SIEMRule,
20
+ SIEMConnectionStatus,
21
+ )
22
+
23
+
24
+ class GoogleSecOpsSIEMClient(SIEMClient):
25
+ """Google SecOps (Chronicle) implementation of the SIEMClient interface.
26
+
27
+ Uses Chronicle APIs:
28
+ - Auth: OAuth 2.0 with service account JWT
29
+ - Search: POST /v1alpha/events:udmSearch
30
+ - Detections: GET /v1alpha/detections
31
+ - Rules: GET /v1alpha/rules
32
+ """
33
+
34
+ # Chronicle API regions
35
+ REGIONS = {
36
+ 'us': 'https://backstory.googleapis.com',
37
+ 'europe': 'https://europe-backstory.googleapis.com',
38
+ 'asia-southeast1': 'https://asia-southeast1-backstory.googleapis.com',
39
+ }
40
+
41
+ def __init__(
42
+ self,
43
+ credentials_json: str,
44
+ customer_id: str,
45
+ region: str = 'us',
46
+ project_id: Optional[str] = None,
47
+ verify_ssl: bool = True,
48
+ ):
49
+ """Initialize Google SecOps client.
50
+
51
+ Args:
52
+ credentials_json: Service account JSON key (as string)
53
+ customer_id: Chronicle customer ID
54
+ region: Chronicle region ('us', 'europe', 'asia-southeast1')
55
+ project_id: Google Cloud project ID (optional, extracted from creds if not provided)
56
+ verify_ssl: Verify SSL certificates
57
+ """
58
+ self.customer_id = customer_id
59
+ self.region = region.lower()
60
+ self.verify_ssl = verify_ssl
61
+ self._access_token: Optional[str] = None
62
+ self._token_expiry: Optional[datetime] = None
63
+
64
+ # Parse service account credentials
65
+ try:
66
+ if isinstance(credentials_json, str):
67
+ self._credentials = json.loads(credentials_json)
68
+ else:
69
+ self._credentials = credentials_json
70
+ except json.JSONDecodeError as e:
71
+ raise ValueError(f"Invalid service account JSON: {e}")
72
+
73
+ self.project_id = project_id or self._credentials.get('project_id', '')
74
+
75
+ # Set API base URL
76
+ self.api_base = self.REGIONS.get(self.region, self.REGIONS['us'])
77
+
78
+ @classmethod
79
+ def from_config(cls, config: Dict[str, Any]) -> 'GoogleSecOpsSIEMClient':
80
+ """Create client from configuration dictionary.
81
+
82
+ Args:
83
+ config: Dict with credentials_json, customer_id, region, etc.
84
+
85
+ Returns:
86
+ GoogleSecOpsSIEMClient instance
87
+ """
88
+ return cls(
89
+ credentials_json=config.get('credentials_json', '{}'),
90
+ customer_id=config.get('customer_id', ''),
91
+ region=config.get('region', 'us'),
92
+ project_id=config.get('project_id'),
93
+ verify_ssl=config.get('verify_ssl', True),
94
+ )
95
+
96
+ @property
97
+ def siem_type(self) -> str:
98
+ """Return the SIEM type identifier."""
99
+ return 'google_secops'
100
+
101
+ def _create_jwt(self) -> str:
102
+ """Create a signed JWT for service account authentication.
103
+
104
+ Returns:
105
+ Signed JWT string
106
+ """
107
+ from cryptography.hazmat.primitives import hashes, serialization
108
+ from cryptography.hazmat.primitives.asymmetric import padding
109
+ from cryptography.hazmat.backends import default_backend
110
+
111
+ now = int(time.time())
112
+ expiry = now + 3600 # 1 hour
113
+
114
+ # JWT header
115
+ header = {
116
+ 'alg': 'RS256',
117
+ 'typ': 'JWT',
118
+ 'kid': self._credentials.get('private_key_id', '')
119
+ }
120
+
121
+ # JWT claims
122
+ claims = {
123
+ 'iss': self._credentials.get('client_email', ''),
124
+ 'sub': self._credentials.get('client_email', ''),
125
+ 'aud': 'https://oauth2.googleapis.com/token',
126
+ 'iat': now,
127
+ 'exp': expiry,
128
+ 'scope': 'https://www.googleapis.com/auth/chronicle-backstory'
129
+ }
130
+
131
+ # Encode header and claims
132
+ def b64_encode(data: dict) -> str:
133
+ return base64.urlsafe_b64encode(
134
+ json.dumps(data, separators=(',', ':')).encode()
135
+ ).rstrip(b'=').decode()
136
+
137
+ header_b64 = b64_encode(header)
138
+ claims_b64 = b64_encode(claims)
139
+ message = f"{header_b64}.{claims_b64}".encode()
140
+
141
+ # Sign with private key
142
+ private_key_pem = self._credentials.get('private_key', '')
143
+ private_key = serialization.load_pem_private_key(
144
+ private_key_pem.encode(),
145
+ password=None,
146
+ backend=default_backend()
147
+ )
148
+
149
+ signature = private_key.sign(
150
+ message,
151
+ padding.PKCS1v15(),
152
+ hashes.SHA256()
153
+ )
154
+ signature_b64 = base64.urlsafe_b64encode(signature).rstrip(b'=').decode()
155
+
156
+ return f"{header_b64}.{claims_b64}.{signature_b64}"
157
+
158
+ def _get_access_token(self) -> str:
159
+ """Get Google OAuth access token using service account.
160
+
161
+ Returns:
162
+ Access token string
163
+ """
164
+ # Check cached token
165
+ if self._access_token and self._token_expiry:
166
+ if datetime.now() < self._token_expiry:
167
+ return self._access_token
168
+
169
+ # Create signed JWT
170
+ jwt = self._create_jwt()
171
+
172
+ # Exchange JWT for access token
173
+ response = requests.post(
174
+ 'https://oauth2.googleapis.com/token',
175
+ data={
176
+ 'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
177
+ 'assertion': jwt,
178
+ },
179
+ timeout=30,
180
+ verify=self.verify_ssl
181
+ )
182
+ response.raise_for_status()
183
+
184
+ token_data = response.json()
185
+ self._access_token = token_data['access_token']
186
+ expires_in = token_data.get('expires_in', 3600)
187
+ self._token_expiry = datetime.now() + timedelta(seconds=expires_in - 60)
188
+
189
+ return self._access_token
190
+
191
+ def _request(
192
+ self,
193
+ method: str,
194
+ endpoint: str,
195
+ params: Optional[Dict] = None,
196
+ json_data: Optional[Dict] = None,
197
+ ) -> requests.Response:
198
+ """Make authenticated API request.
199
+
200
+ Args:
201
+ method: HTTP method
202
+ endpoint: API endpoint (relative to api_base)
203
+ params: Query parameters
204
+ json_data: JSON request body
205
+
206
+ Returns:
207
+ Response object
208
+ """
209
+ token = self._get_access_token()
210
+ url = f"{self.api_base}{endpoint}"
211
+
212
+ headers = {
213
+ 'Authorization': f'Bearer {token}',
214
+ 'Content-Type': 'application/json',
215
+ }
216
+
217
+ response = requests.request(
218
+ method=method,
219
+ url=url,
220
+ headers=headers,
221
+ params=params,
222
+ json=json_data,
223
+ verify=self.verify_ssl,
224
+ timeout=60
225
+ )
226
+ return response
227
+
228
+ def test_connection(self) -> SIEMConnectionStatus:
229
+ """Test connection to Google SecOps.
230
+
231
+ Returns:
232
+ SIEMConnectionStatus with connection details
233
+ """
234
+ try:
235
+ # Try to get access token first (validates credentials)
236
+ self._get_access_token()
237
+
238
+ # Query for a small time window to verify API access
239
+ response = self._request(
240
+ 'GET',
241
+ '/v1alpha/detect/rules',
242
+ params={'page_size': 1}
243
+ )
244
+
245
+ if response.status_code == 200:
246
+ return SIEMConnectionStatus(
247
+ connected=True,
248
+ version='Chronicle API v1alpha',
249
+ siem_type='google_secops',
250
+ details={
251
+ 'region': self.region,
252
+ 'customer_id': self.customer_id,
253
+ 'project_id': self.project_id,
254
+ }
255
+ )
256
+ elif response.status_code == 403:
257
+ return SIEMConnectionStatus(
258
+ connected=False,
259
+ error='Permission denied. Check service account permissions.',
260
+ siem_type='google_secops'
261
+ )
262
+ else:
263
+ return SIEMConnectionStatus(
264
+ connected=False,
265
+ error=f'API error: {response.status_code} - {response.text[:200]}',
266
+ siem_type='google_secops'
267
+ )
268
+
269
+ except requests.exceptions.ConnectionError as e:
270
+ return SIEMConnectionStatus(
271
+ connected=False,
272
+ error=f'Connection failed: {str(e)}',
273
+ siem_type='google_secops'
274
+ )
275
+ except ValueError as e:
276
+ return SIEMConnectionStatus(
277
+ connected=False,
278
+ error=f'Configuration error: {str(e)}',
279
+ siem_type='google_secops'
280
+ )
281
+ except Exception as e:
282
+ return SIEMConnectionStatus(
283
+ connected=False,
284
+ error=str(e),
285
+ siem_type='google_secops'
286
+ )
287
+
288
+ def get_alerts(
289
+ self,
290
+ start_time: datetime,
291
+ end_time: datetime,
292
+ source_ip: Optional[str] = None,
293
+ dest_ip: Optional[str] = None,
294
+ rule_ids: Optional[List[str]] = None,
295
+ search_text: Optional[str] = None,
296
+ limit: int = 100
297
+ ) -> List[SIEMAlert]:
298
+ """Query detections/alerts from Google SecOps.
299
+
300
+ Args:
301
+ start_time: Start of time range
302
+ end_time: End of time range
303
+ source_ip: Filter by source IP
304
+ dest_ip: Filter by destination IP
305
+ rule_ids: Filter by rule IDs
306
+ search_text: Free text search
307
+ limit: Maximum number of results
308
+
309
+ Returns:
310
+ List of normalized SIEMAlert objects
311
+ """
312
+ # Format times for Chronicle API (RFC 3339)
313
+ start_str = start_time.strftime('%Y-%m-%dT%H:%M:%SZ')
314
+ end_str = end_time.strftime('%Y-%m-%dT%H:%M:%SZ')
315
+
316
+ # Query detections endpoint
317
+ params = {
318
+ 'start_time': start_str,
319
+ 'end_time': end_str,
320
+ 'page_size': min(limit, 1000),
321
+ }
322
+
323
+ response = self._request(
324
+ 'GET',
325
+ '/v1alpha/detect/detections',
326
+ params=params
327
+ )
328
+
329
+ if response.status_code != 200:
330
+ return []
331
+
332
+ data = response.json()
333
+ detections = data.get('detections', [])
334
+
335
+ # Filter and normalize results
336
+ alerts = []
337
+ for detection in detections:
338
+ alert = self._normalize_alert(detection)
339
+
340
+ # Apply filters
341
+ if source_ip and alert.source_ip != source_ip:
342
+ continue
343
+ if dest_ip and alert.dest_ip != dest_ip:
344
+ continue
345
+ if rule_ids and alert.rule_id not in rule_ids:
346
+ continue
347
+ if search_text:
348
+ search_lower = search_text.lower()
349
+ if (search_lower not in alert.rule_name.lower() and
350
+ search_lower not in alert.description.lower()):
351
+ continue
352
+
353
+ alerts.append(alert)
354
+
355
+ if len(alerts) >= limit:
356
+ break
357
+
358
+ return alerts
359
+
360
+ def _normalize_alert(self, detection: Dict[str, Any]) -> SIEMAlert:
361
+ """Convert Chronicle detection to normalized SIEMAlert.
362
+
363
+ Args:
364
+ detection: Raw detection from Chronicle API
365
+
366
+ Returns:
367
+ Normalized SIEMAlert
368
+ """
369
+ # Parse timestamp
370
+ timestamp_str = detection.get('detectionTime', '')
371
+ try:
372
+ timestamp = datetime.fromisoformat(timestamp_str.replace('Z', '+00:00'))
373
+ except (ValueError, AttributeError):
374
+ timestamp = datetime.now()
375
+
376
+ # Extract rule info
377
+ rule_info = detection.get('detection', [{}])[0] if detection.get('detection') else {}
378
+ rule_id = rule_info.get('ruleId', detection.get('ruleId', ''))
379
+ rule_name = rule_info.get('ruleName', detection.get('ruleName', rule_id))
380
+
381
+ # Map severity
382
+ severity_raw = detection.get('severity', rule_info.get('severity', 'INFORMATIONAL'))
383
+ severity = self._map_severity(severity_raw)
384
+
385
+ # Extract IPs from UDM events
386
+ source_ip = None
387
+ dest_ip = None
388
+ events = detection.get('collectionElements', [])
389
+ for element in events:
390
+ references = element.get('references', [])
391
+ for ref in references:
392
+ event = ref.get('event', {})
393
+ principal = event.get('principal', {})
394
+ target = event.get('target', {})
395
+
396
+ if not source_ip and principal.get('ip'):
397
+ ips = principal.get('ip', [])
398
+ source_ip = ips[0] if ips else None
399
+
400
+ if not dest_ip and target.get('ip'):
401
+ ips = target.get('ip', [])
402
+ dest_ip = ips[0] if ips else None
403
+
404
+ # Extract description
405
+ description = detection.get('description', rule_info.get('ruleText', ''))
406
+ if not description:
407
+ description = f"Chronicle detection: {rule_name}"
408
+
409
+ return SIEMAlert(
410
+ id=detection.get('id', str(hash(str(detection)))[:12]),
411
+ timestamp=timestamp,
412
+ rule_id=str(rule_id),
413
+ rule_name=str(rule_name),
414
+ severity=severity,
415
+ source_ip=source_ip,
416
+ dest_ip=dest_ip,
417
+ description=str(description)[:200],
418
+ raw_data=detection,
419
+ mitre_tactics=[],
420
+ mitre_techniques=[],
421
+ )
422
+
423
+ def _map_severity(self, severity: str) -> str:
424
+ """Map Chronicle severity to normalized severity."""
425
+ severity_upper = str(severity).upper()
426
+ severity_map = {
427
+ 'CRITICAL': 'critical',
428
+ 'HIGH': 'high',
429
+ 'MEDIUM': 'medium',
430
+ 'LOW': 'low',
431
+ 'INFORMATIONAL': 'info',
432
+ 'INFO': 'info',
433
+ }
434
+ return severity_map.get(severity_upper, 'info')
435
+
436
+ def get_rules(
437
+ self,
438
+ rule_ids: Optional[List[str]] = None,
439
+ enabled_only: bool = True
440
+ ) -> List[SIEMRule]:
441
+ """Get YARA-L detection rules from Google SecOps.
442
+
443
+ Args:
444
+ rule_ids: Optional list of specific rule IDs
445
+ enabled_only: Only return enabled rules
446
+
447
+ Returns:
448
+ List of normalized SIEMRule objects
449
+ """
450
+ response = self._request(
451
+ 'GET',
452
+ '/v1alpha/detect/rules',
453
+ params={'page_size': 1000}
454
+ )
455
+
456
+ if response.status_code != 200:
457
+ return []
458
+
459
+ data = response.json()
460
+ raw_rules = data.get('rules', [])
461
+
462
+ rules = []
463
+ for raw_rule in raw_rules:
464
+ rule_id = raw_rule.get('ruleId', '')
465
+
466
+ # Filter by rule_ids if provided
467
+ if rule_ids and rule_id not in rule_ids:
468
+ continue
469
+
470
+ # Check if enabled
471
+ is_enabled = raw_rule.get('liveRuleEnabled', True)
472
+ if enabled_only and not is_enabled:
473
+ continue
474
+
475
+ rule = SIEMRule(
476
+ id=rule_id,
477
+ name=raw_rule.get('ruleName', rule_id),
478
+ description=raw_rule.get('metadata', {}).get('description', ''),
479
+ severity=self._map_severity(raw_rule.get('metadata', {}).get('severity', '')),
480
+ enabled=is_enabled,
481
+ mitre_tactics=raw_rule.get('metadata', {}).get('mitreTactics', []),
482
+ mitre_techniques=raw_rule.get('metadata', {}).get('mitreTechniques', []),
483
+ raw_data=raw_rule,
484
+ )
485
+ rules.append(rule)
486
+
487
+ return rules
488
+
489
+ def get_recommended_rules(self, attack_type: str) -> List[Dict[str, Any]]:
490
+ """Get recommended rules for detecting an attack type.
491
+
492
+ Args:
493
+ attack_type: Tool/attack name (e.g., 'nmap', 'hydra')
494
+
495
+ Returns:
496
+ List of rule recommendations
497
+ """
498
+ # Chronicle/Google SecOps rule recommendations
499
+ recommendations_map = {
500
+ 'nmap': [
501
+ {
502
+ 'rule_id': 'network_port_scan',
503
+ 'rule_name': 'Network Port Scan Detection',
504
+ 'yaral': '''
505
+ rule network_port_scan {
506
+ meta:
507
+ description = "Detects potential port scanning activity"
508
+ severity = "MEDIUM"
509
+ events:
510
+ $e.metadata.event_type = "NETWORK_CONNECTION"
511
+ $e.principal.ip = $src_ip
512
+ match:
513
+ $src_ip over 5m
514
+ condition:
515
+ #e > 100
516
+ }''',
517
+ },
518
+ ],
519
+ 'hydra': [
520
+ {
521
+ 'rule_id': 'brute_force_auth',
522
+ 'rule_name': 'Brute Force Authentication',
523
+ 'yaral': '''
524
+ rule brute_force_authentication {
525
+ meta:
526
+ description = "Detects brute force login attempts"
527
+ severity = "HIGH"
528
+ events:
529
+ $e.metadata.event_type = "USER_LOGIN"
530
+ $e.security_result.action = "BLOCK"
531
+ $e.principal.ip = $src_ip
532
+ match:
533
+ $src_ip over 5m
534
+ condition:
535
+ #e > 10
536
+ }''',
537
+ },
538
+ ],
539
+ 'sqlmap': [
540
+ {
541
+ 'rule_id': 'sql_injection',
542
+ 'rule_name': 'SQL Injection Attempt',
543
+ 'yaral': '''
544
+ rule sql_injection_attempt {
545
+ meta:
546
+ description = "Detects SQL injection patterns in requests"
547
+ severity = "HIGH"
548
+ events:
549
+ $e.metadata.event_type = "NETWORK_HTTP"
550
+ re.regex($e.target.url, `(?i)(union|select|insert|update|delete|drop).*`)
551
+ condition:
552
+ $e
553
+ }''',
554
+ },
555
+ ],
556
+ }
557
+
558
+ attack_lower = attack_type.lower()
559
+ recommendations = recommendations_map.get(attack_lower, [])
560
+
561
+ return [
562
+ {
563
+ 'rule_id': r['rule_id'],
564
+ 'rule_name': r['rule_name'],
565
+ 'description': f"YARA-L rule for detecting {attack_type}",
566
+ 'severity': 'high',
567
+ 'enabled': False, # These are recommendations, not deployed
568
+ 'siem_type': 'google_secops',
569
+ 'yaral_rule': r.get('yaral', ''),
570
+ }
571
+ for r in recommendations
572
+ ]
573
+
574
+ def search_udm_events(
575
+ self,
576
+ query: str,
577
+ start_time: datetime,
578
+ end_time: datetime,
579
+ limit: int = 100
580
+ ) -> List[Dict[str, Any]]:
581
+ """Search UDM events with a custom query.
582
+
583
+ This is a Chronicle-specific method for advanced queries.
584
+
585
+ Args:
586
+ query: UDM search query
587
+ start_time: Start of time range
588
+ end_time: End of time range
589
+ limit: Maximum results
590
+
591
+ Returns:
592
+ List of UDM events
593
+ """
594
+ start_str = start_time.strftime('%Y-%m-%dT%H:%M:%SZ')
595
+ end_str = end_time.strftime('%Y-%m-%dT%H:%M:%SZ')
596
+
597
+ response = self._request(
598
+ 'POST',
599
+ '/v1alpha/events:udmSearch',
600
+ json_data={
601
+ 'query': query,
602
+ 'time_range': {
603
+ 'start_time': start_str,
604
+ 'end_time': end_str,
605
+ },
606
+ 'limit': limit,
607
+ }
608
+ )
609
+
610
+ if response.status_code != 200:
611
+ return []
612
+
613
+ data = response.json()
614
+ return data.get('events', {}).get('events', [])
@@ -10,8 +10,8 @@ from pathlib import Path
10
10
  from souleyez.storage.database import get_db
11
11
  from souleyez.storage.crypto import get_crypto_manager
12
12
 
13
- # Supported SIEM types
14
- SIEM_TYPES = ['wazuh', 'splunk', 'elastic', 'sentinel']
13
+ # Supported SIEM types (Open Source first, then Commercial)
14
+ SIEM_TYPES = ['wazuh', 'elastic', 'splunk', 'sentinel', 'google_secops']
15
15
 
16
16
 
17
17
  class WazuhConfig:
souleyez/main.py CHANGED
@@ -173,7 +173,7 @@ def _check_privileged_tools():
173
173
 
174
174
 
175
175
  @click.group()
176
- @click.version_option(version='2.32.0')
176
+ @click.version_option(version='2.35.0')
177
177
  def cli():
178
178
  """SoulEyez - AI-Powered Pentesting Platform by CyberSoul Security"""
179
179
  from souleyez.log_config import init_logging
@@ -8930,18 +8930,48 @@ def _select_siem_type(engagement_id: int):
8930
8930
  config = WazuhConfig.get_config(engagement_id)
8931
8931
  current_type = config.get('siem_type', 'wazuh') if config else 'wazuh'
8932
8932
 
8933
- # Show available SIEM types
8934
- siem_types = SIEMFactory.get_available_types()
8935
- click.echo(" Available SIEM platforms:")
8933
+ # Define SIEM categories with emojis
8934
+ siem_emojis = {
8935
+ 'wazuh': '🦎',
8936
+ 'elastic': '🦌',
8937
+ 'splunk': '⚡',
8938
+ 'sentinel': '🛡️',
8939
+ 'google_secops': '🔍',
8940
+ }
8941
+ open_source_siems = ['wazuh', 'elastic']
8942
+ commercial_siems = ['splunk', 'sentinel', 'google_secops']
8943
+
8944
+ # Build ordered list for selection (open source first)
8945
+ siem_types = open_source_siems + commercial_siems
8946
+
8947
+ # Show Open Source section
8948
+ click.echo(" 🌐 " + click.style("OPEN SOURCE", fg='green', bold=True))
8949
+ click.echo(" " + "─" * 60)
8950
+ idx = 1
8951
+ for siem_type in open_source_siems:
8952
+ info = SIEMFactory.get_type_info(siem_type)
8953
+ emoji = siem_emojis.get(siem_type, '📊')
8954
+ current_marker = click.style(" (current)", fg='green') if siem_type == current_type else ""
8955
+ # Remove [Open Source] prefix from description since we have section header
8956
+ desc = info['description'].replace('[Open Source] ', '')
8957
+ click.echo(f" [{idx}] {emoji} {click.style(info['name'], bold=True)}{current_marker}")
8958
+ click.echo(f" {click.style(desc, dim=True)}")
8959
+ idx += 1
8936
8960
  click.echo()
8937
8961
 
8938
- for i, siem_type in enumerate(siem_types, 1):
8962
+ # Show Commercial section
8963
+ click.echo(" 💼 " + click.style("COMMERCIAL", fg='cyan', bold=True))
8964
+ click.echo(" " + "─" * 60)
8965
+ for siem_type in commercial_siems:
8939
8966
  info = SIEMFactory.get_type_info(siem_type)
8940
- is_current = " (current)" if siem_type == current_type else ""
8941
- marker = click.style("*", fg='green') if siem_type == current_type else " "
8942
- click.echo(f" {marker} [{i}] {click.style(info['name'], bold=True)}{is_current}")
8943
- click.echo(f" {info['description']}")
8944
- click.echo()
8967
+ emoji = siem_emojis.get(siem_type, '📊')
8968
+ current_marker = click.style(" (current)", fg='green') if siem_type == current_type else ""
8969
+ # Remove [Commercial] prefix from description since we have section header
8970
+ desc = info['description'].replace('[Commercial] ', '')
8971
+ click.echo(f" [{idx}] {emoji} {click.style(info['name'], bold=True)}{current_marker}")
8972
+ click.echo(f" {click.style(desc, dim=True)}")
8973
+ idx += 1
8974
+ click.echo()
8945
8975
 
8946
8976
  click.echo(" [q] Cancel")
8947
8977
  click.echo()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: souleyez
3
- Version: 2.32.0
3
+ Version: 2.35.0
4
4
  Summary: AI-Powered Penetration Testing Platform with 40+ integrated tools
5
5
  Author-email: CyberSoul Security <contact@cybersoulsecurity.com>
6
6
  Maintainer-email: CyberSoul Security <contact@cybersoulsecurity.com>
@@ -78,7 +78,7 @@ Welcome to the SoulEyez beta! Thank you for helping us test and improve this pen
78
78
 
79
79
  > ⚠️ **Important**: Only use SoulEyez on systems you have explicit authorization to test.
80
80
 
81
- ## Version: 2.32.0
81
+ ## Version: 2.35.0
82
82
 
83
83
  ### What's Included
84
84
 
@@ -316,4 +316,4 @@ Happy hacking! 🛡️
316
316
 
317
317
  ---
318
318
 
319
- **Version**: 2.32.0 | **Release Date**: January 2026 | **Maintainer**: CyberSoul Security
319
+ **Version**: 2.35.0 | **Release Date**: January 2026 | **Maintainer**: CyberSoul Security
@@ -1,10 +1,10 @@
1
- souleyez/__init__.py,sha256=kYTzrOjUQok3QAN5C-3OM0LpxmGiH8Rnrv0NH_BrrRo,23
1
+ souleyez/__init__.py,sha256=UQAEIsTm70ZCEpKHmL6kibJZmLc7zEVtPdYXWIXUQvQ,24
2
2
  souleyez/config.py,sha256=av357I3GYRWAklv8Dto-9-5Db699Wq5znez7zo7241Q,11595
3
3
  souleyez/devtools.py,sha256=rptmUY4a5eVvYjdEc6273MSagL-D9xibPOFgohVqUno,3508
4
4
  souleyez/feature_flags.py,sha256=mo6YAq07lc6sR3lEFKmIwTKxXZ2JPxwa5X97uR_mu50,4642
5
5
  souleyez/history.py,sha256=gzs5I_j-3OigIP6yfmBChdqxaFmyUIxvTpzWUPe_Q6c,2853
6
6
  souleyez/log_config.py,sha256=MMhPAJOqgXDfuE-xm5g0RxAfWndcmbhFHvIEMm1a_Wo,5830
7
- souleyez/main.py,sha256=qG2GlC_8xPsKzzWOIDFBXpoY12JFfDoyDX7XRYS9N-w,130228
7
+ souleyez/main.py,sha256=PzOvPmiph3bP8HsTdi1VBaFKNYkCaLnz67aFN0cfPMc,130228
8
8
  souleyez/scanner.py,sha256=U3IWHRrJ5aQ32dSHiVAHB60w1R_z0E0QxfM99msYNlw,3124
9
9
  souleyez/security.py,sha256=S84m1QmnKz_6NgH2I6IBIAorMHxRPNYVFSnks5xjihQ,2479
10
10
  souleyez/ui.py,sha256=15pfsqoDPnojAqr5S0TZHJE2ZkSHzkHpNVfVvsRj66A,34301
@@ -104,7 +104,7 @@ souleyez/detection/__init__.py,sha256=QIhvXjFdjrquQ6A0VQ7GZQkK_EXB59t8Dv9PKXhEUe
104
104
  souleyez/detection/attack_signatures.py,sha256=akgWwiIkh6WYnghCuLhRV0y6FS0SQ0caGF8tZUc49oA,6965
105
105
  souleyez/detection/mitre_mappings.py,sha256=xejE80YK-g8kKaeQoo-vBl8P3t8RTTItbfN0NaVZw6s,20558
106
106
  souleyez/detection/validator.py,sha256=-AJ7QSJ3-6jFKLnPG_Rc34IXyF4JPyI82BFUgTA9zw0,15641
107
- souleyez/docs/README.md,sha256=KLyTnR-Wd5XWsg5-i7LKdru4qfJknI1e1a8Pp7KvXOE,7183
107
+ souleyez/docs/README.md,sha256=IP5bsU6g4NnyMMkLXeI5qSTgPAmhpDxS3-0AOUnFAjw,7183
108
108
  souleyez/docs/api-reference/cli-commands.md,sha256=lTLFnILN3YRVdqCaag7WgsYXfDGglb1TuPexkxDsVdE,12917
109
109
  souleyez/docs/api-reference/engagement-api.md,sha256=nd-EvQMtiJrobg2bzFEADp853HP1Uhb9dmgok0_-neE,11672
110
110
  souleyez/docs/api-reference/integration-guide.md,sha256=c96uX79ukHyYotLa54wZ20Kx-EUZnrKegTeGkfLD-pw,16285
@@ -162,10 +162,11 @@ souleyez/importers/__init__.py,sha256=FNeqzNzKnaO7NUpoHt2IjXpA0g2eGl8YQwwfg2LhFE
162
162
  souleyez/importers/msf_importer.py,sha256=pPeXKqIv5ML2mu2Ew_BFI3vJbqDuBc53OHE4GJmqd5A,11754
163
163
  souleyez/importers/smart_importer.py,sha256=hem7Zhvurl8Lm_qY1qeOF-JigQWClXtdmKKlzavvHbo,14425
164
164
  souleyez/integrations/__init__.py,sha256=AG15oe2CIfQIJCUCOnMifj-EeZdzCe0A764ZU2gKVCo,79
165
- souleyez/integrations/siem/__init__.py,sha256=CVeHwXX8wstISL-0nZgffWgdMJQO1Aji0wVAKX67Ouw,1263
165
+ souleyez/integrations/siem/__init__.py,sha256=6KWX7hgTvoOiD5upV7nmBph1e1WOyrMdtN_JfFRJ_pc,1368
166
166
  souleyez/integrations/siem/base.py,sha256=JmbhRlHl7wsRyd3nOhCqDHFP4VwBv-VR7uIKWoohKDE,8144
167
167
  souleyez/integrations/siem/elastic.py,sha256=LuxheashiOO-AJbtUEK_BZ1T8EUAr1KlZkOheWPPgrQ,14712
168
- souleyez/integrations/siem/factory.py,sha256=aRTV2IFTF1e5PvmTrAVk1UsrVzm476esY2mLsey3F9Q,8061
168
+ souleyez/integrations/siem/factory.py,sha256=8OBoVpVSjnQQ5oXjcq3iJUEK-ItnyCVkcZV8wHbWEug,9424
169
+ souleyez/integrations/siem/googlesecops.py,sha256=HY832skGOlpidH9KdvvSmvMXP-fxT5S90BD6gFwMKnU,19419
169
170
  souleyez/integrations/siem/sentinel.py,sha256=8zxuMgqlfCeGu_gHPNjfmBfBR5PEmtMTbqliC9dnbcI,15789
170
171
  souleyez/integrations/siem/splunk.py,sha256=Vi6RMSqjXCVhy6AmQ0kFs_OrP6jDez3Vis8ZYJtWIGA,28587
171
172
  souleyez/integrations/siem/wazuh.py,sha256=vltBhkP5BkgJLmdmF6u_b2RgTvP7iE9sFDxOt9v71qM,10245
@@ -173,7 +174,7 @@ souleyez/integrations/siem/rule_mappings/__init__.py,sha256=PbCLigl6Zo8ClvxZ29yd
173
174
  souleyez/integrations/siem/rule_mappings/wazuh_rules.py,sha256=tfduSFFVxUOgL19w4ZFmOC0JDlnewz3PwA94a_XFTmI,8307
174
175
  souleyez/integrations/wazuh/__init__.py,sha256=d-zfG_enCStagLZNf3bZ57GujT64jsfI7zS3gJDui10,288
175
176
  souleyez/integrations/wazuh/client.py,sha256=Gc42bYP-dh2sDJNLh2WYt6iK0OUMnE9WYkegaZ_qUwk,21824
176
- souleyez/integrations/wazuh/config.py,sha256=B670Z1jE0q6u8JsmA9YM-k-P66XXe0tjeaGvlvB6HbQ,14866
177
+ souleyez/integrations/wazuh/config.py,sha256=WGOkc53EKZFsriUgs8xK26g6UBGvoGnK1Gcc-cRxDYU,14920
177
178
  souleyez/integrations/wazuh/host_mapper.py,sha256=e5wKLb3KbkWLOry8z-8WMD36hmRSWO6ZhB_-iyfgKjg,9047
178
179
  souleyez/integrations/wazuh/sync.py,sha256=lrZ-9Fd1HKm2ZHPvj83MCFL9UhZqAowWEXTidXDizw0,12168
179
180
  souleyez/intelligence/__init__.py,sha256=oLjyxmSk5VgLxYnkSM9PmrREpiGAVmnqrRBxRGf4cQo,423
@@ -346,7 +347,7 @@ souleyez/ui/export_view.py,sha256=0nQvVsKk7FU4uRzSfJ_qBZh_Lfn8hgGA2rbJ5bNg5-Y,65
346
347
  souleyez/ui/gap_analysis_view.py,sha256=AytAOEBq010wwo9hne1TE-uJpY_xicjLrFANbvN3r3w,30727
347
348
  souleyez/ui/help_system.py,sha256=nKGxLaMi-TKYs6xudTyw_tZqBb1cGFEuYYh6N-MAsJE,16648
348
349
  souleyez/ui/intelligence_view.py,sha256=VeAQ-3mANRnLIVpRqocL3JV0HUmJtADdxDeC5lzQhE0,32168
349
- souleyez/ui/interactive.py,sha256=-f58u20sNMsNPgcPjadPQABuN4vDBI6DY_gl7-3YGRc,1405285
350
+ souleyez/ui/interactive.py,sha256=dkyIys13Q6_OsDO84-sg1DNQvVTttVOBHjIZV2sSM7w,1406611
350
351
  souleyez/ui/interactive_selector.py,sha256=6A51fgmFRnemBY0aCPHIhK2Rpba16NjSGKLzC0Q5vI8,16407
351
352
  souleyez/ui/log_formatter.py,sha256=akhIkYoO_cCaKxS1V5N3iPmIrHzgsU7pmsedx70s9TI,3845
352
353
  souleyez/ui/menu_components.py,sha256=N8zq2QXGmfaLJ08l53MMYt1y-5LRWgpZH6r8nXHonj8,3519
@@ -369,9 +370,9 @@ souleyez/ui/tutorial_state.py,sha256=Thf7_qCj4VKjG7UqgJqa9kjIqiFUU-7Q7kG4v-u2B4A
369
370
  souleyez/ui/wazuh_vulns_view.py,sha256=3vJJEmrjgS2wD6EDB7ZV7WxgytBHTm-1WqNDjp7lVEI,21830
370
371
  souleyez/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
371
372
  souleyez/utils/tool_checker.py,sha256=kQcXJVY5NiO-orQAUnpHhpQvR5UOBNHJ0PaT0fBxYoQ,30782
372
- souleyez-2.32.0.dist-info/licenses/LICENSE,sha256=J7vDD5QMF4w2oSDm35eBgosATE70ah1M40u9W4EpTZs,1090
373
- souleyez-2.32.0.dist-info/METADATA,sha256=2pO4jHAKDY_Jw2kR40v1Iwh00vU0QpGjZ-Cm59pqmx4,11345
374
- souleyez-2.32.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
375
- souleyez-2.32.0.dist-info/entry_points.txt,sha256=bN5W1dhjDZJl3TKclMjRpfQvGPmyrJLwwDuCj_X39HE,48
376
- souleyez-2.32.0.dist-info/top_level.txt,sha256=afAMzS9p4lcdBNxhGo6jl3ipQE9HUvvNIPOdjtPjr_Q,9
377
- souleyez-2.32.0.dist-info/RECORD,,
373
+ souleyez-2.35.0.dist-info/licenses/LICENSE,sha256=J7vDD5QMF4w2oSDm35eBgosATE70ah1M40u9W4EpTZs,1090
374
+ souleyez-2.35.0.dist-info/METADATA,sha256=hdL5BqKlvdFWr9zsEZpIB7i5lubTY_C_jrSu2fYdsvA,11345
375
+ souleyez-2.35.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
376
+ souleyez-2.35.0.dist-info/entry_points.txt,sha256=bN5W1dhjDZJl3TKclMjRpfQvGPmyrJLwwDuCj_X39HE,48
377
+ souleyez-2.35.0.dist-info/top_level.txt,sha256=afAMzS9p4lcdBNxhGo6jl3ipQE9HUvvNIPOdjtPjr_Q,9
378
+ souleyez-2.35.0.dist-info/RECORD,,