contentctl 4.3.0__py3-none-any.whl → 4.3.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.
@@ -1060,7 +1060,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1060
1060
  results = JSONResultsReader(job.results(output_mode="json"))
1061
1061
 
1062
1062
  # Consolidate a set of the distinct observable field names
1063
- observable_fields_set = set([o.name for o in detection.tags.observable])
1063
+ observable_fields_set = set([o.name for o in detection.tags.observable]) # keeping this around for later
1064
+ risk_object_fields_set = set([o.name for o in detection.tags.observable if "Victim" in o.role ]) # just the "Risk Objects"
1065
+ threat_object_fields_set = set([o.name for o in detection.tags.observable if "Attacker" in o.role]) # just the "threat objects"
1064
1066
 
1065
1067
  # Ensure the search had at least one result
1066
1068
  if int(job.content.get("resultCount", "0")) > 0:
@@ -1068,7 +1070,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1068
1070
  test.result = UnitTestResult()
1069
1071
 
1070
1072
  # Initialize the collection of fields that are empty that shouldn't be
1073
+ present_threat_objects: set[str] = set()
1071
1074
  empty_fields: set[str] = set()
1075
+
1076
+
1072
1077
 
1073
1078
  # Filter out any messages in the results
1074
1079
  for result in results:
@@ -1077,30 +1082,50 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1077
1082
 
1078
1083
  # If not a message, it is a dict and we will process it
1079
1084
  results_fields_set = set(result.keys())
1085
+ # Guard against first events (relevant later)
1080
1086
 
1081
- # Identify any observable fields that are not available in the results
1082
- missing_fields = observable_fields_set - results_fields_set
1083
- if len(missing_fields) > 0:
1087
+ # Identify any risk object fields that are not available in the results
1088
+ missing_risk_objects = risk_object_fields_set - results_fields_set
1089
+ if len(missing_risk_objects) > 0:
1084
1090
  # Report a failure in such cases
1085
- e = Exception(f"The observable field(s) {missing_fields} are missing in the detection results")
1091
+ e = Exception(f"The observable field(s) {missing_risk_objects} are missing in the detection results")
1086
1092
  test.result.set_job_content(
1087
1093
  job.content,
1088
1094
  self.infrastructure,
1089
- TestResultStatus.ERROR,
1095
+ TestResultStatus.FAIL,
1090
1096
  exception=e,
1091
1097
  duration=time.time() - search_start_time,
1092
1098
  )
1093
1099
 
1094
- return
1100
+ return
1095
1101
 
1096
- # If we find one or more fields that contain the string "null" then they were
1102
+ # If we find one or more risk object fields that contain the string "null" then they were
1097
1103
  # not populated and we should throw an error. This can happen if there is a typo
1098
1104
  # on a field. In this case, the field will appear but will not contain any values
1099
- current_empty_fields = set()
1105
+ current_empty_fields: set[str] = set()
1106
+
1100
1107
  for field in observable_fields_set:
1101
1108
  if result.get(field, 'null') == 'null':
1102
- current_empty_fields.add(field)
1103
-
1109
+ if field in risk_object_fields_set:
1110
+ e = Exception(f"The risk object field {field} is missing in at least one result.")
1111
+ test.result.set_job_content(
1112
+ job.content,
1113
+ self.infrastructure,
1114
+ TestResultStatus.FAIL,
1115
+ exception=e,
1116
+ duration=time.time() - search_start_time,
1117
+ )
1118
+ return
1119
+ else:
1120
+ if field in threat_object_fields_set:
1121
+ current_empty_fields.add(field)
1122
+ else:
1123
+ if field in threat_object_fields_set:
1124
+ present_threat_objects.add(field)
1125
+ continue
1126
+
1127
+
1128
+
1104
1129
  # If everything succeeded up until now, and no empty fields are found in the
1105
1130
  # current result, then the search was a success
1106
1131
  if len(current_empty_fields) == 0:
@@ -1114,21 +1139,32 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1114
1139
 
1115
1140
  else:
1116
1141
  empty_fields = empty_fields.union(current_empty_fields)
1117
-
1118
- # Report a failure if there were empty fields in all results
1119
- e = Exception(
1120
- f"One or more required observable fields {empty_fields} contained 'null' values. Is the "
1121
- "data being parsed correctly or is there an error in the naming of a field?"
1142
+
1143
+
1144
+ missing_threat_objects = threat_object_fields_set - present_threat_objects
1145
+ # Report a failure if there were empty fields in a threat object in all results
1146
+ if len(missing_threat_objects) > 0:
1147
+ e = Exception(
1148
+ f"One or more required threat object fields {missing_threat_objects} contained 'null' values in all events. "
1149
+ "Is the data being parsed correctly or is there an error in the naming of a field?"
1122
1150
  )
1123
- test.result.set_job_content(
1124
- job.content,
1125
- self.infrastructure,
1126
- TestResultStatus.ERROR,
1127
- exception=e,
1128
- duration=time.time() - search_start_time,
1129
- )
1151
+ test.result.set_job_content(
1152
+ job.content,
1153
+ self.infrastructure,
1154
+ TestResultStatus.FAIL,
1155
+ exception=e,
1156
+ duration=time.time() - search_start_time,
1157
+ )
1158
+ return
1159
+
1130
1160
 
1131
- return
1161
+ test.result.set_job_content(
1162
+ job.content,
1163
+ self.infrastructure,
1164
+ TestResultStatus.PASS,
1165
+ duration=time.time() - search_start_time,
1166
+ )
1167
+ return
1132
1168
 
1133
1169
  else:
1134
1170
  # Report a failure if there were no results at all
@@ -290,6 +290,11 @@ class Detection_Abstract(SecurityContentObject):
290
290
  risk_object['threat_object_field'] = entity.name
291
291
  risk_object['threat_object_type'] = "url"
292
292
  risk_objects.append(risk_object)
293
+
294
+ elif 'Attacker' in entity.role:
295
+ risk_object['threat_object_field'] = entity.name
296
+ risk_object['threat_object_type'] = entity.type.lower()
297
+ risk_objects.append(risk_object)
293
298
 
294
299
  else:
295
300
  risk_object['risk_object_type'] = 'other'
@@ -132,3 +132,8 @@ SES_ATTACK_TACTICS_ID_MAPPING = {
132
132
  "Exfiltration": "TA0010",
133
133
  "Impact": "TA0040"
134
134
  }
135
+
136
+ RBA_OBSERVABLE_ROLE_MAPPING = {
137
+ "Attacker": 0,
138
+ "Victim": 1
139
+ }
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel, field_validator
2
- from contentctl.objects.constants import SES_OBSERVABLE_TYPE_MAPPING, SES_OBSERVABLE_ROLE_MAPPING
2
+ from contentctl.objects.constants import SES_OBSERVABLE_TYPE_MAPPING, RBA_OBSERVABLE_ROLE_MAPPING
3
3
 
4
4
 
5
5
  class Observable(BaseModel):
@@ -26,10 +26,12 @@ class Observable(BaseModel):
26
26
  def check_roles(cls, v: list[str]):
27
27
  if len(v) == 0:
28
28
  raise ValueError("Error, at least 1 role must be listed for Observable.")
29
+ if len(v) > 1:
30
+ raise ValueError("Error, each Observable can only have one role.")
29
31
  for role in v:
30
- if role not in SES_OBSERVABLE_ROLE_MAPPING.keys():
32
+ if role not in RBA_OBSERVABLE_ROLE_MAPPING.keys():
31
33
  raise ValueError(
32
34
  f"Invalid role '{role}' provided for observable. Valid observable types are "
33
- f"{SES_OBSERVABLE_ROLE_MAPPING.keys()}"
35
+ f"{RBA_OBSERVABLE_ROLE_MAPPING.keys()}"
34
36
  )
35
37
  return v
@@ -53,11 +53,11 @@ tags:
53
53
  - name: parent_process_name
54
54
  type: Process
55
55
  role:
56
- - Parent Process
56
+ - Attacker
57
57
  - name: process_name
58
58
  type: Process
59
59
  role:
60
- - Child Process
60
+ - Attacker
61
61
  product:
62
62
  - Splunk Enterprise
63
63
  - Splunk Enterprise Security
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: contentctl
3
- Version: 4.3.0
3
+ Version: 4.3.1
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -22,7 +22,7 @@ Requires-Dist: pygit2 (>=1.14.1,<2.0.0)
22
22
  Requires-Dist: questionary (>=2.0.1,<3.0.0)
23
23
  Requires-Dist: requests (>=2.32.2,<2.33.0)
24
24
  Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
25
- Requires-Dist: setuptools (>=69.5.1,<73.0.0)
25
+ Requires-Dist: setuptools (>=69.5.1,<74.0.0)
26
26
  Requires-Dist: splunk-sdk (>=2.0.1,<3.0.0)
27
27
  Requires-Dist: tqdm (>=4.66.4,<5.0.0)
28
28
  Requires-Dist: tyro (>=0.8.3,<0.9.0)
@@ -4,7 +4,7 @@ contentctl/actions/deploy_acs.py,sha256=mf3uk495H1EU_LNN-TiOsYCo18HMGoEBMb6ojeTr
4
4
  contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=zg8JasDjCpSC-yhseEyUwO8qbDJIUJbhlus9Li9ZAnA,8818
5
5
  contentctl/actions/detection_testing/GitService.py,sha256=W1vnDDt8JvIL7Z1Lve3D3RS7h8qwMxrW0BMXVGuDZDM,9007
6
6
  contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=N5mznaeErVak3mOBwsd0RDBFJO3bku0EZvpayCyU-uk,2259
7
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=fDiyntUFXGi3OKNCL02Pr-4PLzX3dKWcD5UiTYoOkYA,53002
7
+ contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=1PxEnhWSFgiOtIlqRD10gRShjB65i9vLiFEnwHSGf4o,55139
8
8
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py,sha256=REM3WB-DQAczeknGAKMzJhnvHgnt-u9yDG2UKGVj2vM,6854
9
9
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py,sha256=Q1ZfCYOp54O39bgTScZMInkmZiU-bGAM9Hiwr2mq5ms,370
10
10
  contentctl/actions/detection_testing/progress_bar.py,sha256=OK9oRnPlzPAswt9KZNYID-YLHxqaYPY821kIE4-rCeA,3244
@@ -33,7 +33,7 @@ contentctl/helper/utils.py,sha256=8ICRvE7DUiNL9BK4Hw71hCLFbd3R2u86OwKeDOdaBTY,19
33
33
  contentctl/input/director.py,sha256=kTqdN_rCzRMn4dR32hPaVyx2llhAxyhJgoGjowhsHzs,10887
34
34
  contentctl/input/new_content_questions.py,sha256=o4prlBoUhEMxqpZukquI9WKbzfFJfYhEF7a8m2q_BEE,5565
35
35
  contentctl/input/yml_reader.py,sha256=hyVUYhx4Ka8C618kP2D_E3sDUKEQGC6ty_QZQArHKd4,1489
36
- contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=gra5kZkLFATBQJ-ZR4SNzkZ86MBX3pnOz2RdIl5oOVs,34502
36
+ contentctl/objects/abstract_security_content_objects/detection_abstract.py,sha256=V3pglFS5HYdPURIQFdNlHQfXYYr7-xLClrXiMUsb9rw,34745
37
37
  contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py,sha256=7tv-WEiUUOvZkao272J9l1IvL0y12kJ6SWLsMeWv9VE,9820
38
38
  contentctl/objects/alert_action.py,sha256=E9gjCn5C31h0sN7k90KNe4agRxFFSnMW_Z-Ri_3YQss,1335
39
39
  contentctl/objects/atomic.py,sha256=BP27gP8KHeODp6UazhVFxwDQ64wuJCARGsLfIH34h7U,8768
@@ -42,7 +42,7 @@ contentctl/objects/base_test_result.py,sha256=ZEAC2IUwUrW_-zHoaS7zp-uBBKIVTS8TcM
42
42
  contentctl/objects/baseline.py,sha256=Lb1vJKtDdlDrzWgrdkC9oQao_TnRrOxSwOWHf4trtaU,2150
43
43
  contentctl/objects/baseline_tags.py,sha256=fVhLF-NmisavybB_idu3N0Con0Ymj8clKfRMkWzBB-k,1762
44
44
  contentctl/objects/config.py,sha256=XpCjYIoU4XTM6RL4Nt-YjMX342FJz4R-ATDXJWexHNs,43615
45
- contentctl/objects/constants.py,sha256=1LjiK9A7t0aHHkJz2mrW-DImdW1P98GPssTwmwNNI_M,3468
45
+ contentctl/objects/constants.py,sha256=lfCcr1DsTZvANHj4Ee1_sEV-SebHwAn41-5EvmoEX2E,3537
46
46
  contentctl/objects/correlation_search.py,sha256=QZp1u-dwTZl9hkUOlJdHQ9h4Hp2bDHWWCKtrp3mvIUY,48310
47
47
  contentctl/objects/data_source.py,sha256=aRr6lHu-EtGmi6J2nXKD7i2ozUPtp7X-vDkQiutvD3I,1545
48
48
  contentctl/objects/deployment.py,sha256=Qc6M4yeOvxjqFKR8sfjd4CG06AbVheTOqP1mwqo4t8s,2651
@@ -66,7 +66,7 @@ contentctl/objects/macro.py,sha256=9nE-bxkFhtaltHOUCr0luU8jCCthmglHjhKs6Q2YzLU,2
66
66
  contentctl/objects/mitre_attack_enrichment.py,sha256=JqSDnKF0-ZTaxUgvhdYNzIAt-7kNaEBvGr_5Bbfdwr8,1072
67
67
  contentctl/objects/notable_action.py,sha256=ValkblBaG-60TF19y_vSnNzoNZ3eg48wIfr0qZxyKTA,1605
68
68
  contentctl/objects/notable_event.py,sha256=ITcwLzeatSGpe8267PYN-EhgqOSoWTfciCBVu8zjOXE,682
69
- contentctl/objects/observable.py,sha256=loEkmo7RPl383Jq-i5BmSnAqpTeh80d6ai7PDeWuxF0,1211
69
+ contentctl/objects/observable.py,sha256=pw0Ehi_KMb7nXzw2kuw1FnCknpD8zDkCAqBTa-M_F28,1313
70
70
  contentctl/objects/playbook.py,sha256=hSYYpdMhctgpp7uwaPciFqu1yuFI4M1NHy1WBBLyvzM,2469
71
71
  contentctl/objects/playbook_tags.py,sha256=NrhTGcgoYSGEZggrfebko0GBOXN9x05IadRUUL_CVfQ,1436
72
72
  contentctl/objects/risk_analysis_action.py,sha256=Glzcq99DAqqOJ2eZYCkUI3R5hA5cZGU0ZuCSinFf2R8,4278
@@ -157,14 +157,14 @@ contentctl/templates/deployments/escu_default_configuration_hunting.yml,sha256=h
157
157
  contentctl/templates/deployments/escu_default_configuration_ttp.yml,sha256=1D-pvzaH1v3_yCZXaY6njmdvV4S2_Ak8uzzCOsnj9XY,548
158
158
  contentctl/templates/detections/application/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
159
159
  contentctl/templates/detections/cloud/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
160
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml,sha256=-jp7CC3shnA9Te_0Zw6jLbLT8JnrVQvOfEUkNCQbCNo,3322
160
+ contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml,sha256=tw5_HVqMyx6itht6v2fz6Uqoy3EoIJ_lzVlrRABrMhY,3311
161
161
  contentctl/templates/detections/network/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
162
  contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
163
163
  contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
164
164
  contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
165
165
  contentctl/templates/stories/cobalt_strike.yml,sha256=rlaXxMN-5k8LnKBLPafBoksyMtlmsPMHPJOjTiMiZ-M,3063
166
- contentctl-4.3.0.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
- contentctl-4.3.0.dist-info/METADATA,sha256=YpQM8untSb4LvHmv0IHCjgJP7qM5X_c13n17lJGK_kg,20939
168
- contentctl-4.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
- contentctl-4.3.0.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
- contentctl-4.3.0.dist-info/RECORD,,
166
+ contentctl-4.3.1.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
+ contentctl-4.3.1.dist-info/METADATA,sha256=MAmOisMABa1nqU_QRdevnCbhYfgBWH8N3q441doHiTc,20939
168
+ contentctl-4.3.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
+ contentctl-4.3.1.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
+ contentctl-4.3.1.dist-info/RECORD,,