Skip to content

Commit 6826bcc

Browse files
authored
Merge pull request #234 from splunk/threat_objects
Threat objects Note - this is merged despite some CICD job failures because PyPI is a mess right now and throwing many HTTPS Connection issues.
2 parents 5d84999 + a67d48b commit 6826bcc

File tree

2 files changed

+65
-24
lines changed

2 files changed

+65
-24
lines changed

contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,15 +1060,20 @@ def retry_search_until_timeout(
10601060
results = JSONResultsReader(job.results(output_mode="json"))
10611061

10621062
# 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"
10641066

10651067
# Ensure the search had at least one result
10661068
if int(job.content.get("resultCount", "0")) > 0:
10671069
# Initialize the test result
10681070
test.result = UnitTestResult()
10691071

10701072
# Initialize the collection of fields that are empty that shouldn't be
1073+
present_threat_objects: set[str] = set()
10711074
empty_fields: set[str] = set()
1075+
1076+
10721077

10731078
# Filter out any messages in the results
10741079
for result in results:
@@ -1077,30 +1082,50 @@ def retry_search_until_timeout(
10771082

10781083
# If not a message, it is a dict and we will process it
10791084
results_fields_set = set(result.keys())
1085+
# Guard against first events (relevant later)
10801086

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:
10841090
# 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")
10861092
test.result.set_job_content(
10871093
job.content,
10881094
self.infrastructure,
1089-
TestResultStatus.ERROR,
1095+
TestResultStatus.FAIL,
10901096
exception=e,
10911097
duration=time.time() - search_start_time,
10921098
)
10931099

1094-
return
1100+
return
10951101

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
10971103
# not populated and we should throw an error. This can happen if there is a typo
10981104
# 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+
11001107
for field in observable_fields_set:
11011108
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+
11041129
# If everything succeeded up until now, and no empty fields are found in the
11051130
# current result, then the search was a success
11061131
if len(current_empty_fields) == 0:
@@ -1114,21 +1139,32 @@ def retry_search_until_timeout(
11141139

11151140
else:
11161141
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?"
11221150
)
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+
11301160

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
11321168

11331169
else:
11341170
# Report a failure if there were no results at all

contentctl/objects/abstract_security_content_objects/detection_abstract.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,11 @@ def risk(self) -> list[dict[str, Any]]:
290290
risk_object['threat_object_field'] = entity.name
291291
risk_object['threat_object_type'] = "url"
292292
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)
293298

294299
else:
295300
risk_object['risk_object_type'] = 'other'

0 commit comments

Comments
 (0)