contentctl 4.4.7__py3-none-any.whl → 5.0.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.
- contentctl/__init__.py +1 -1
- contentctl/actions/build.py +102 -57
- contentctl/actions/deploy_acs.py +29 -24
- contentctl/actions/detection_testing/DetectionTestingManager.py +66 -42
- contentctl/actions/detection_testing/GitService.py +134 -76
- contentctl/actions/detection_testing/generate_detection_coverage_badge.py +48 -30
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +192 -147
- contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +45 -32
- contentctl/actions/detection_testing/progress_bar.py +9 -6
- contentctl/actions/detection_testing/views/DetectionTestingView.py +16 -19
- contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +1 -5
- contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +2 -2
- contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +1 -4
- contentctl/actions/doc_gen.py +9 -5
- contentctl/actions/initialize.py +45 -33
- contentctl/actions/inspect.py +118 -61
- contentctl/actions/new_content.py +155 -108
- contentctl/actions/release_notes.py +276 -146
- contentctl/actions/reporting.py +23 -19
- contentctl/actions/test.py +33 -28
- contentctl/actions/validate.py +55 -34
- contentctl/api.py +54 -45
- contentctl/contentctl.py +124 -90
- contentctl/enrichments/attack_enrichment.py +112 -72
- contentctl/enrichments/cve_enrichment.py +34 -28
- contentctl/enrichments/splunk_app_enrichment.py +38 -36
- contentctl/helper/link_validator.py +101 -78
- contentctl/helper/splunk_app.py +69 -41
- contentctl/helper/utils.py +58 -53
- contentctl/input/director.py +68 -36
- contentctl/input/new_content_questions.py +27 -35
- contentctl/input/yml_reader.py +28 -18
- contentctl/objects/abstract_security_content_objects/detection_abstract.py +303 -259
- contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +115 -52
- contentctl/objects/alert_action.py +10 -9
- contentctl/objects/annotated_types.py +1 -1
- contentctl/objects/atomic.py +65 -54
- contentctl/objects/base_test.py +5 -3
- contentctl/objects/base_test_result.py +19 -11
- contentctl/objects/baseline.py +62 -30
- contentctl/objects/baseline_tags.py +30 -24
- contentctl/objects/config.py +790 -597
- contentctl/objects/constants.py +33 -56
- contentctl/objects/correlation_search.py +150 -136
- contentctl/objects/dashboard.py +55 -41
- contentctl/objects/data_source.py +16 -17
- contentctl/objects/deployment.py +43 -44
- contentctl/objects/deployment_email.py +3 -2
- contentctl/objects/deployment_notable.py +4 -2
- contentctl/objects/deployment_phantom.py +7 -6
- contentctl/objects/deployment_rba.py +3 -2
- contentctl/objects/deployment_scheduling.py +3 -2
- contentctl/objects/deployment_slack.py +3 -2
- contentctl/objects/detection.py +5 -2
- contentctl/objects/detection_metadata.py +1 -0
- contentctl/objects/detection_stanza.py +7 -2
- contentctl/objects/detection_tags.py +58 -103
- contentctl/objects/drilldown.py +66 -34
- contentctl/objects/enums.py +81 -100
- contentctl/objects/errors.py +16 -24
- contentctl/objects/integration_test.py +3 -3
- contentctl/objects/integration_test_result.py +1 -0
- contentctl/objects/investigation.py +59 -36
- contentctl/objects/investigation_tags.py +30 -19
- contentctl/objects/lookup.py +304 -101
- contentctl/objects/macro.py +55 -39
- contentctl/objects/manual_test.py +3 -3
- contentctl/objects/manual_test_result.py +1 -0
- contentctl/objects/mitre_attack_enrichment.py +17 -16
- contentctl/objects/notable_action.py +2 -1
- contentctl/objects/notable_event.py +1 -3
- contentctl/objects/playbook.py +37 -35
- contentctl/objects/playbook_tags.py +23 -13
- contentctl/objects/rba.py +96 -0
- contentctl/objects/risk_analysis_action.py +15 -11
- contentctl/objects/risk_event.py +110 -160
- contentctl/objects/risk_object.py +1 -0
- contentctl/objects/savedsearches_conf.py +9 -7
- contentctl/objects/security_content_object.py +5 -2
- contentctl/objects/story.py +54 -49
- contentctl/objects/story_tags.py +56 -45
- contentctl/objects/test_attack_data.py +2 -1
- contentctl/objects/test_group.py +5 -2
- contentctl/objects/threat_object.py +1 -0
- contentctl/objects/throttling.py +27 -18
- contentctl/objects/unit_test.py +3 -4
- contentctl/objects/unit_test_baseline.py +5 -5
- contentctl/objects/unit_test_result.py +6 -6
- contentctl/output/api_json_output.py +233 -220
- contentctl/output/attack_nav_output.py +21 -21
- contentctl/output/attack_nav_writer.py +29 -37
- contentctl/output/conf_output.py +235 -172
- contentctl/output/conf_writer.py +201 -125
- contentctl/output/data_source_writer.py +38 -26
- contentctl/output/doc_md_output.py +53 -27
- contentctl/output/jinja_writer.py +19 -15
- contentctl/output/json_writer.py +21 -11
- contentctl/output/svg_output.py +56 -38
- contentctl/output/templates/analyticstories_detections.j2 +2 -2
- contentctl/output/templates/analyticstories_stories.j2 +1 -1
- contentctl/output/templates/collections.j2 +1 -1
- contentctl/output/templates/doc_detections.j2 +0 -5
- contentctl/output/templates/es_investigations_investigations.j2 +1 -1
- contentctl/output/templates/es_investigations_stories.j2 +1 -1
- contentctl/output/templates/savedsearches_baselines.j2 +2 -2
- contentctl/output/templates/savedsearches_detections.j2 +10 -11
- contentctl/output/templates/savedsearches_investigations.j2 +2 -2
- contentctl/output/templates/transforms.j2 +6 -8
- contentctl/output/yml_writer.py +29 -20
- contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
- contentctl/templates/stories/cobalt_strike.yml +1 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/METADATA +5 -4
- contentctl-5.0.0.dist-info/RECORD +168 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/WHEEL +1 -1
- contentctl/actions/initialize_old.py +0 -245
- contentctl/objects/event_source.py +0 -11
- contentctl/objects/observable.py +0 -37
- contentctl/output/detection_writer.py +0 -28
- contentctl/output/new_content_yml_output.py +0 -56
- contentctl/output/yml_output.py +0 -66
- contentctl-4.4.7.dist-info/RECORD +0 -173
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/LICENSE.md +0 -0
- {contentctl-4.4.7.dist-info → contentctl-5.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import uuid
|
|
3
1
|
import abc
|
|
4
|
-
import os.path
|
|
5
2
|
import configparser
|
|
6
|
-
import json
|
|
7
3
|
import datetime
|
|
8
|
-
import
|
|
4
|
+
import json
|
|
5
|
+
import os.path
|
|
9
6
|
import pathlib
|
|
10
|
-
|
|
7
|
+
import time
|
|
8
|
+
import urllib.parse
|
|
9
|
+
import uuid
|
|
10
|
+
from shutil import copyfile
|
|
11
11
|
from ssl import SSLEOFError, SSLZeroReturnError
|
|
12
12
|
from sys import stdout
|
|
13
|
-
from
|
|
14
|
-
from typing import
|
|
13
|
+
from tempfile import TemporaryDirectory, mktemp
|
|
14
|
+
from typing import Optional, Union
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
import splunklib.client as client # type: ignore
|
|
19
|
-
from splunklib.binding import HTTPError # type: ignore
|
|
20
|
-
from splunklib.results import JSONResultsReader, Message # type: ignore
|
|
16
|
+
import requests # type: ignore
|
|
17
|
+
import splunklib.client as client # type: ignore
|
|
21
18
|
import splunklib.results
|
|
19
|
+
import tqdm # type: ignore
|
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, dataclasses
|
|
21
|
+
from splunklib.binding import HTTPError # type: ignore
|
|
22
|
+
from splunklib.results import JSONResultsReader, Message # type: ignore
|
|
22
23
|
from urllib3 import disable_warnings
|
|
23
|
-
import urllib.parse
|
|
24
24
|
|
|
25
|
-
from contentctl.
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
from contentctl.actions.detection_testing.progress_bar import (
|
|
26
|
+
FinalTestingStates,
|
|
27
|
+
TestingStates,
|
|
28
|
+
TestReportingType,
|
|
29
|
+
format_pbar_string,
|
|
30
|
+
)
|
|
31
|
+
from contentctl.helper.utils import Utils
|
|
28
32
|
from contentctl.objects.base_test import BaseTest
|
|
29
|
-
from contentctl.objects.
|
|
33
|
+
from contentctl.objects.base_test_result import TestResultStatus
|
|
34
|
+
from contentctl.objects.config import Infrastructure, test_common
|
|
35
|
+
from contentctl.objects.correlation_search import CorrelationSearch, PbarData
|
|
36
|
+
from contentctl.objects.detection import Detection
|
|
37
|
+
from contentctl.objects.enums import AnalyticsType, PostTestBehavior
|
|
30
38
|
from contentctl.objects.integration_test import IntegrationTest
|
|
31
|
-
from contentctl.objects.test_attack_data import TestAttackData
|
|
32
|
-
from contentctl.objects.unit_test_result import UnitTestResult
|
|
33
39
|
from contentctl.objects.integration_test_result import IntegrationTestResult
|
|
40
|
+
from contentctl.objects.test_attack_data import TestAttackData
|
|
34
41
|
from contentctl.objects.test_group import TestGroup
|
|
35
|
-
from contentctl.objects.
|
|
36
|
-
from contentctl.objects.
|
|
37
|
-
from contentctl.helper.utils import Utils
|
|
38
|
-
from contentctl.actions.detection_testing.progress_bar import (
|
|
39
|
-
format_pbar_string,
|
|
40
|
-
TestReportingType,
|
|
41
|
-
FinalTestingStates,
|
|
42
|
-
TestingStates
|
|
43
|
-
)
|
|
42
|
+
from contentctl.objects.unit_test import UnitTest
|
|
43
|
+
from contentctl.objects.unit_test_result import UnitTestResult
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class SetupTestGroupResults(BaseModel):
|
|
@@ -48,9 +48,7 @@ class SetupTestGroupResults(BaseModel):
|
|
|
48
48
|
success: bool = True
|
|
49
49
|
duration: float = 0
|
|
50
50
|
start_time: float
|
|
51
|
-
model_config = ConfigDict(
|
|
52
|
-
arbitrary_types_allowed=True
|
|
53
|
-
)
|
|
51
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
54
52
|
|
|
55
53
|
|
|
56
54
|
class CleanupTestGroupResults(BaseModel):
|
|
@@ -60,26 +58,31 @@ class CleanupTestGroupResults(BaseModel):
|
|
|
60
58
|
|
|
61
59
|
class ContainerStoppedException(Exception):
|
|
62
60
|
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
63
|
class CannotRunBaselineException(Exception):
|
|
64
|
-
# Support for testing detections with baselines
|
|
64
|
+
# Support for testing detections with baselines
|
|
65
65
|
# does not currently exist in contentctl.
|
|
66
|
-
# As such, whenever we encounter a detection
|
|
66
|
+
# As such, whenever we encounter a detection
|
|
67
67
|
# with baselines we should generate a descriptive
|
|
68
68
|
# exception
|
|
69
69
|
pass
|
|
70
70
|
|
|
71
|
+
|
|
71
72
|
class ReplayIndexDoesNotExistOnServer(Exception):
|
|
72
|
-
|
|
73
|
+
"""
|
|
73
74
|
In order to replay data files into the Splunk Server
|
|
74
75
|
for testing, they must be replayed into an index that
|
|
75
76
|
exists. If that index does not exist, this error will
|
|
76
77
|
be generated and raised before we try to do anything else
|
|
77
78
|
with that Data File.
|
|
78
|
-
|
|
79
|
+
"""
|
|
80
|
+
|
|
79
81
|
pass
|
|
80
82
|
|
|
83
|
+
|
|
81
84
|
@dataclasses.dataclass(frozen=False)
|
|
82
|
-
class DetectionTestingManagerOutputDto
|
|
85
|
+
class DetectionTestingManagerOutputDto:
|
|
83
86
|
inputQueue: list[Detection] = Field(default_factory=list)
|
|
84
87
|
outputQueue: list[Detection] = Field(default_factory=list)
|
|
85
88
|
currentTestingQueue: dict[str, Union[Detection, None]] = Field(default_factory=dict)
|
|
@@ -101,9 +104,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
101
104
|
_conn: client.Service = PrivateAttr()
|
|
102
105
|
pbar: tqdm.tqdm = None
|
|
103
106
|
start_time: Optional[float] = None
|
|
104
|
-
model_config = ConfigDict(
|
|
105
|
-
arbitrary_types_allowed=True
|
|
106
|
-
)
|
|
107
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
107
108
|
|
|
108
109
|
def __init__(self, **data):
|
|
109
110
|
super().__init__(**data)
|
|
@@ -131,7 +132,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
131
132
|
bar_format=f"{self.get_name()} starting",
|
|
132
133
|
miniters=0,
|
|
133
134
|
mininterval=0,
|
|
134
|
-
file=stdout
|
|
135
|
+
file=stdout,
|
|
135
136
|
)
|
|
136
137
|
|
|
137
138
|
self.start_time = time.time()
|
|
@@ -140,14 +141,16 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
140
141
|
(self.start, "Starting"),
|
|
141
142
|
(self.get_conn, "Waiting for App Installation"),
|
|
142
143
|
(self.configure_conf_file_datamodels, "Configuring Datamodels"),
|
|
143
|
-
(
|
|
144
|
+
(
|
|
145
|
+
self.create_replay_index,
|
|
146
|
+
f"Create index '{self.sync_obj.replay_index}'",
|
|
147
|
+
),
|
|
144
148
|
(self.get_all_indexes, "Getting all indexes from server"),
|
|
145
149
|
(self.configure_imported_roles, "Configuring Roles"),
|
|
146
150
|
(self.configure_delete_indexes, "Configuring Indexes"),
|
|
147
151
|
(self.configure_hec, "Configuring HEC"),
|
|
148
|
-
(self.wait_for_ui_ready, "Finishing Setup")
|
|
152
|
+
(self.wait_for_ui_ready, "Finishing Setup"),
|
|
149
153
|
]:
|
|
150
|
-
|
|
151
154
|
self.format_pbar_string(
|
|
152
155
|
TestReportingType.SETUP,
|
|
153
156
|
self.get_name(),
|
|
@@ -162,7 +165,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
162
165
|
self.finish()
|
|
163
166
|
return
|
|
164
167
|
|
|
165
|
-
self.format_pbar_string(
|
|
168
|
+
self.format_pbar_string(
|
|
169
|
+
TestReportingType.SETUP, self.get_name(), "Finished Setup!"
|
|
170
|
+
)
|
|
166
171
|
|
|
167
172
|
def wait_for_ui_ready(self):
|
|
168
173
|
self.get_conn()
|
|
@@ -184,7 +189,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
184
189
|
name="DETECTION_TESTING_HEC",
|
|
185
190
|
kind="http",
|
|
186
191
|
index=self.sync_obj.replay_index,
|
|
187
|
-
indexes=",".join(
|
|
192
|
+
indexes=",".join(
|
|
193
|
+
self.all_indexes_on_server
|
|
194
|
+
), # This allows the HEC to write to all indexes
|
|
188
195
|
useACK=True,
|
|
189
196
|
)
|
|
190
197
|
self.hec_token = str(res.token)
|
|
@@ -234,7 +241,6 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
234
241
|
while True:
|
|
235
242
|
self.check_for_teardown()
|
|
236
243
|
try:
|
|
237
|
-
|
|
238
244
|
conn = client.connect(
|
|
239
245
|
host=self.infrastructure.instance_address,
|
|
240
246
|
port=self.infrastructure.api_port,
|
|
@@ -277,7 +283,6 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
277
283
|
time.sleep(1)
|
|
278
284
|
|
|
279
285
|
def create_replay_index(self):
|
|
280
|
-
|
|
281
286
|
try:
|
|
282
287
|
self.get_conn().indexes.create(name=self.sync_obj.replay_index)
|
|
283
288
|
except HTTPError as e:
|
|
@@ -292,7 +297,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
292
297
|
self,
|
|
293
298
|
imported_roles: list[str] = ["user", "power", "can_delete"],
|
|
294
299
|
enterprise_security_roles: list[str] = ["ess_admin", "ess_analyst", "ess_user"],
|
|
295
|
-
):
|
|
300
|
+
):
|
|
296
301
|
try:
|
|
297
302
|
# Set which roles should be configured. For Enterprise Security/Integration Testing,
|
|
298
303
|
# we must add some extra foles.
|
|
@@ -334,9 +339,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
334
339
|
self.check_for_teardown()
|
|
335
340
|
time.sleep(1)
|
|
336
341
|
try:
|
|
337
|
-
_ = self.get_conn().get(
|
|
338
|
-
f"configs/conf-{conf_file_name}", app=app_name
|
|
339
|
-
)
|
|
342
|
+
_ = self.get_conn().get(f"configs/conf-{conf_file_name}", app=app_name)
|
|
340
343
|
return
|
|
341
344
|
except Exception:
|
|
342
345
|
pass
|
|
@@ -366,7 +369,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
366
369
|
parser.read(custom_acceleration_datamodels)
|
|
367
370
|
if len(parser.keys()) > 1:
|
|
368
371
|
self.pbar.write(
|
|
369
|
-
f"Read {len(parser)-1} custom datamodels from {str(custom_acceleration_datamodels)}!"
|
|
372
|
+
f"Read {len(parser) - 1} custom datamodels from {str(custom_acceleration_datamodels)}!"
|
|
370
373
|
)
|
|
371
374
|
|
|
372
375
|
if not cim_acceleration_datamodels.is_file():
|
|
@@ -414,11 +417,15 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
414
417
|
try:
|
|
415
418
|
self.test_detection(detection)
|
|
416
419
|
except ContainerStoppedException:
|
|
417
|
-
self.pbar.write(
|
|
420
|
+
self.pbar.write(
|
|
421
|
+
f"Warning - container was stopped when trying to execute detection [{self.get_name()}]"
|
|
422
|
+
)
|
|
418
423
|
self.finish()
|
|
419
424
|
return
|
|
420
425
|
except Exception as e:
|
|
421
|
-
self.pbar.write(
|
|
426
|
+
self.pbar.write(
|
|
427
|
+
f"Error testing detection: {type(e).__name__}: {str(e)}"
|
|
428
|
+
)
|
|
422
429
|
raise e
|
|
423
430
|
finally:
|
|
424
431
|
self.sync_obj.outputQueue.append(detection)
|
|
@@ -442,7 +449,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
442
449
|
self.format_pbar_string(
|
|
443
450
|
TestReportingType.GROUP,
|
|
444
451
|
test_group.name,
|
|
445
|
-
FinalTestingStates.SKIP
|
|
452
|
+
FinalTestingStates.SKIP,
|
|
446
453
|
start_time=time.time(),
|
|
447
454
|
set_pbar=False,
|
|
448
455
|
)
|
|
@@ -460,22 +467,32 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
460
467
|
detection,
|
|
461
468
|
test_group.integration_test,
|
|
462
469
|
setup_results,
|
|
463
|
-
test_group.unit_test.result
|
|
470
|
+
test_group.unit_test.result,
|
|
464
471
|
)
|
|
465
472
|
|
|
466
473
|
# cleanup
|
|
467
|
-
cleanup_results = self.cleanup_test_group(
|
|
474
|
+
cleanup_results = self.cleanup_test_group(
|
|
475
|
+
test_group, setup_results.start_time
|
|
476
|
+
)
|
|
468
477
|
|
|
469
478
|
# update the results duration w/ the setup/cleanup time (for those not skipped)
|
|
470
|
-
if (test_group.unit_test.result is not None) and (
|
|
479
|
+
if (test_group.unit_test.result is not None) and (
|
|
480
|
+
not test_group.unit_test_skipped()
|
|
481
|
+
):
|
|
471
482
|
test_group.unit_test.result.duration = round(
|
|
472
|
-
test_group.unit_test.result.duration
|
|
473
|
-
|
|
483
|
+
test_group.unit_test.result.duration
|
|
484
|
+
+ setup_results.duration
|
|
485
|
+
+ cleanup_results.duration,
|
|
486
|
+
2,
|
|
474
487
|
)
|
|
475
|
-
if (test_group.integration_test.result is not None) and (
|
|
488
|
+
if (test_group.integration_test.result is not None) and (
|
|
489
|
+
not test_group.integration_test_skipped()
|
|
490
|
+
):
|
|
476
491
|
test_group.integration_test.result.duration = round(
|
|
477
|
-
test_group.integration_test.result.duration
|
|
478
|
-
|
|
492
|
+
test_group.integration_test.result.duration
|
|
493
|
+
+ setup_results.duration
|
|
494
|
+
+ cleanup_results.duration,
|
|
495
|
+
2,
|
|
479
496
|
)
|
|
480
497
|
|
|
481
498
|
# Write test group status
|
|
@@ -483,7 +500,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
483
500
|
self.format_pbar_string(
|
|
484
501
|
TestReportingType.GROUP,
|
|
485
502
|
test_group.name,
|
|
486
|
-
TestingStates.DONE_GROUP
|
|
503
|
+
TestingStates.DONE_GROUP,
|
|
487
504
|
start_time=setup_results.start_time,
|
|
488
505
|
set_pbar=False,
|
|
489
506
|
)
|
|
@@ -504,8 +521,8 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
504
521
|
self.format_pbar_string(
|
|
505
522
|
TestReportingType.GROUP,
|
|
506
523
|
test_group.name,
|
|
507
|
-
TestingStates.BEGINNING_GROUP
|
|
508
|
-
start_time=setup_start_time
|
|
524
|
+
TestingStates.BEGINNING_GROUP,
|
|
525
|
+
start_time=setup_start_time,
|
|
509
526
|
)
|
|
510
527
|
# https://github.com/WoLpH/python-progressbar/issues/164
|
|
511
528
|
# Use NullBar if there is more than 1 container or we are running
|
|
@@ -544,7 +561,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
544
561
|
self.format_pbar_string(
|
|
545
562
|
TestReportingType.GROUP,
|
|
546
563
|
test_group.name,
|
|
547
|
-
TestingStates.DELETING
|
|
564
|
+
TestingStates.DELETING,
|
|
548
565
|
start_time=test_group_start_time,
|
|
549
566
|
)
|
|
550
567
|
|
|
@@ -554,8 +571,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
554
571
|
|
|
555
572
|
# Return the cleanup metadata, adding start time and duration
|
|
556
573
|
return CleanupTestGroupResults(
|
|
557
|
-
duration=time.time() - cleanup_start_time,
|
|
558
|
-
start_time=cleanup_start_time
|
|
574
|
+
duration=time.time() - cleanup_start_time, start_time=cleanup_start_time
|
|
559
575
|
)
|
|
560
576
|
|
|
561
577
|
def format_pbar_string(
|
|
@@ -589,17 +605,12 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
589
605
|
|
|
590
606
|
# invoke the helper method
|
|
591
607
|
new_string = format_pbar_string(
|
|
592
|
-
self.pbar,
|
|
593
|
-
test_reporting_type,
|
|
594
|
-
test_name,
|
|
595
|
-
state,
|
|
596
|
-
start_time,
|
|
597
|
-
set_pbar
|
|
608
|
+
self.pbar, test_reporting_type, test_name, state, start_time, set_pbar
|
|
598
609
|
)
|
|
599
610
|
|
|
600
611
|
# update sync status if needed
|
|
601
612
|
if update_sync_status:
|
|
602
|
-
self.sync_obj.currentTestingQueue[self.get_name()] = {
|
|
613
|
+
self.sync_obj.currentTestingQueue[self.get_name()] = { # type: ignore
|
|
603
614
|
"name": state,
|
|
604
615
|
"search": "N/A",
|
|
605
616
|
}
|
|
@@ -612,7 +623,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
612
623
|
detection: Detection,
|
|
613
624
|
test: UnitTest,
|
|
614
625
|
setup_results: SetupTestGroupResults,
|
|
615
|
-
FORCE_ALL_TIME: bool = True
|
|
626
|
+
FORCE_ALL_TIME: bool = True,
|
|
616
627
|
):
|
|
617
628
|
"""
|
|
618
629
|
Execute a unit test and set its results appropriately
|
|
@@ -632,7 +643,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
632
643
|
self.format_pbar_string(
|
|
633
644
|
TestReportingType.UNIT,
|
|
634
645
|
f"{detection.name}:{test.name}",
|
|
635
|
-
FinalTestingStates.SKIP
|
|
646
|
+
FinalTestingStates.SKIP,
|
|
636
647
|
start_time=test_start_time,
|
|
637
648
|
set_pbar=False,
|
|
638
649
|
)
|
|
@@ -656,7 +667,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
656
667
|
self.infrastructure,
|
|
657
668
|
TestResultStatus.ERROR,
|
|
658
669
|
exception=setup_results.exception,
|
|
659
|
-
duration=time.time() - test_start_time
|
|
670
|
+
duration=time.time() - test_start_time,
|
|
660
671
|
)
|
|
661
672
|
|
|
662
673
|
# report the failure to the CLI
|
|
@@ -664,7 +675,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
664
675
|
self.format_pbar_string(
|
|
665
676
|
TestReportingType.UNIT,
|
|
666
677
|
f"{detection.name}:{test.name}",
|
|
667
|
-
FinalTestingStates.ERROR
|
|
678
|
+
FinalTestingStates.ERROR,
|
|
668
679
|
start_time=test_start_time,
|
|
669
680
|
set_pbar=False,
|
|
670
681
|
)
|
|
@@ -686,10 +697,12 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
686
697
|
try:
|
|
687
698
|
# Iterate over baselines (if any)
|
|
688
699
|
for baseline in detection.baselines:
|
|
689
|
-
raise CannotRunBaselineException(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
700
|
+
raise CannotRunBaselineException(
|
|
701
|
+
"Detection requires Execution of a Baseline, "
|
|
702
|
+
"however Baseline execution is not "
|
|
703
|
+
"currently supported in contentctl. Mark "
|
|
704
|
+
"this as manual_test."
|
|
705
|
+
)
|
|
693
706
|
self.retry_search_until_timeout(detection, test, kwargs, test_start_time)
|
|
694
707
|
except CannotRunBaselineException as e:
|
|
695
708
|
# Init the test result and record a failure if there was an issue during the search
|
|
@@ -699,7 +712,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
699
712
|
self.infrastructure,
|
|
700
713
|
TestResultStatus.ERROR,
|
|
701
714
|
exception=e,
|
|
702
|
-
duration=time.time() - test_start_time
|
|
715
|
+
duration=time.time() - test_start_time,
|
|
703
716
|
)
|
|
704
717
|
except ContainerStoppedException as e:
|
|
705
718
|
raise e
|
|
@@ -712,7 +725,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
712
725
|
self.infrastructure,
|
|
713
726
|
TestResultStatus.ERROR,
|
|
714
727
|
exception=e,
|
|
715
|
-
duration=time.time() - test_start_time
|
|
728
|
+
duration=time.time() - test_start_time,
|
|
716
729
|
)
|
|
717
730
|
|
|
718
731
|
# Pause here if the terminate flag has NOT been set AND either of the below are true:
|
|
@@ -724,7 +737,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
724
737
|
res = "ERROR"
|
|
725
738
|
link = detection.search
|
|
726
739
|
else:
|
|
727
|
-
res = test.result.status.
|
|
740
|
+
res = test.result.status.upper() # type: ignore
|
|
728
741
|
link = test.result.get_summary_dict()["sid_link"]
|
|
729
742
|
|
|
730
743
|
self.format_pbar_string(
|
|
@@ -746,7 +759,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
746
759
|
test.result = UnitTestResult(
|
|
747
760
|
message=message,
|
|
748
761
|
exception=ValueError(message),
|
|
749
|
-
status=TestResultStatus.ERROR
|
|
762
|
+
status=TestResultStatus.ERROR,
|
|
750
763
|
)
|
|
751
764
|
|
|
752
765
|
# Report a pass
|
|
@@ -755,7 +768,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
755
768
|
self.format_pbar_string(
|
|
756
769
|
TestReportingType.UNIT,
|
|
757
770
|
f"{detection.name}:{test.name}",
|
|
758
|
-
FinalTestingStates.PASS
|
|
771
|
+
FinalTestingStates.PASS,
|
|
759
772
|
start_time=test_start_time,
|
|
760
773
|
set_pbar=False,
|
|
761
774
|
)
|
|
@@ -766,7 +779,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
766
779
|
self.format_pbar_string(
|
|
767
780
|
TestReportingType.UNIT,
|
|
768
781
|
f"{detection.name}:{test.name}",
|
|
769
|
-
FinalTestingStates.SKIP
|
|
782
|
+
FinalTestingStates.SKIP,
|
|
770
783
|
start_time=test_start_time,
|
|
771
784
|
set_pbar=False,
|
|
772
785
|
)
|
|
@@ -777,7 +790,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
777
790
|
self.format_pbar_string(
|
|
778
791
|
TestReportingType.UNIT,
|
|
779
792
|
f"{detection.name}:{test.name}",
|
|
780
|
-
FinalTestingStates.FAIL
|
|
793
|
+
FinalTestingStates.FAIL,
|
|
781
794
|
start_time=test_start_time,
|
|
782
795
|
set_pbar=False,
|
|
783
796
|
)
|
|
@@ -788,7 +801,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
788
801
|
self.format_pbar_string(
|
|
789
802
|
TestReportingType.UNIT,
|
|
790
803
|
f"{detection.name}:{test.name}",
|
|
791
|
-
FinalTestingStates.ERROR
|
|
804
|
+
FinalTestingStates.ERROR,
|
|
792
805
|
start_time=test_start_time,
|
|
793
806
|
set_pbar=False,
|
|
794
807
|
)
|
|
@@ -811,7 +824,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
811
824
|
detection: Detection,
|
|
812
825
|
test: IntegrationTest,
|
|
813
826
|
setup_results: SetupTestGroupResults,
|
|
814
|
-
unit_test_result: Optional[UnitTestResult]
|
|
827
|
+
unit_test_result: Optional[UnitTestResult],
|
|
815
828
|
):
|
|
816
829
|
"""
|
|
817
830
|
Executes an integration test on the detection
|
|
@@ -821,7 +834,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
821
834
|
test_start_time = time.time()
|
|
822
835
|
|
|
823
836
|
# First, check to see if the test should be skipped (Hunting or Correlation)
|
|
824
|
-
if detection.type in [AnalyticsType.Hunting
|
|
837
|
+
if detection.type in [AnalyticsType.Hunting, AnalyticsType.Correlation]:
|
|
825
838
|
test.skip(
|
|
826
839
|
f"TEST SKIPPED: detection is type {detection.type} and cannot be integration "
|
|
827
840
|
"tested at this time"
|
|
@@ -843,11 +856,11 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
843
856
|
# Determine the reporting state (we should only encounter SKIP/FAIL/ERROR)
|
|
844
857
|
state: str
|
|
845
858
|
if test.result.status == TestResultStatus.SKIP:
|
|
846
|
-
state = FinalTestingStates.SKIP
|
|
859
|
+
state = FinalTestingStates.SKIP
|
|
847
860
|
elif test.result.status == TestResultStatus.FAIL:
|
|
848
|
-
state = FinalTestingStates.FAIL
|
|
861
|
+
state = FinalTestingStates.FAIL
|
|
849
862
|
elif test.result.status == TestResultStatus.ERROR:
|
|
850
|
-
state = FinalTestingStates.ERROR
|
|
863
|
+
state = FinalTestingStates.ERROR
|
|
851
864
|
else:
|
|
852
865
|
raise ValueError(
|
|
853
866
|
f"Status for (integration) '{detection.name}:{test.name}' was preemptively set"
|
|
@@ -883,7 +896,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
883
896
|
),
|
|
884
897
|
exception=setup_results.exception,
|
|
885
898
|
duration=round(time.time() - test_start_time, 2),
|
|
886
|
-
status=TestResultStatus.ERROR
|
|
899
|
+
status=TestResultStatus.ERROR,
|
|
887
900
|
)
|
|
888
901
|
|
|
889
902
|
# report the failure to the CLI
|
|
@@ -891,7 +904,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
891
904
|
self.format_pbar_string(
|
|
892
905
|
TestReportingType.INTEGRATION,
|
|
893
906
|
f"{detection.name}:{test.name}",
|
|
894
|
-
FinalTestingStates.FAIL
|
|
907
|
+
FinalTestingStates.FAIL,
|
|
895
908
|
start_time=test_start_time,
|
|
896
909
|
set_pbar=False,
|
|
897
910
|
)
|
|
@@ -905,7 +918,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
905
918
|
pbar_data = PbarData(
|
|
906
919
|
pbar=self.pbar,
|
|
907
920
|
fq_test_name=f"{detection.name}:{test.name}",
|
|
908
|
-
start_time=test_start_time
|
|
921
|
+
start_time=test_start_time,
|
|
909
922
|
)
|
|
910
923
|
|
|
911
924
|
# TODO (#228): consider reusing CorrelationSearch instances across test cases
|
|
@@ -923,7 +936,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
923
936
|
test.result = IntegrationTestResult(
|
|
924
937
|
message="TEST ERROR: unhandled exception in CorrelationSearch",
|
|
925
938
|
exception=e,
|
|
926
|
-
status=TestResultStatus.ERROR
|
|
939
|
+
status=TestResultStatus.ERROR,
|
|
927
940
|
)
|
|
928
941
|
|
|
929
942
|
# TODO (#229): when in interactive mode, cleanup should happen after user interaction
|
|
@@ -935,7 +948,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
935
948
|
if test.result is None:
|
|
936
949
|
res = "ERROR"
|
|
937
950
|
else:
|
|
938
|
-
res = test.result.status.
|
|
951
|
+
res = test.result.status.upper() # type: ignore
|
|
939
952
|
|
|
940
953
|
# Get the link to the saved search in this specific instance
|
|
941
954
|
link = f"https://{self.infrastructure.instance_address}:{self.infrastructure.web_ui_port}"
|
|
@@ -959,7 +972,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
959
972
|
test.result = IntegrationTestResult(
|
|
960
973
|
message=message,
|
|
961
974
|
exception=ValueError(message),
|
|
962
|
-
status=TestResultStatus.ERROR
|
|
975
|
+
status=TestResultStatus.ERROR,
|
|
963
976
|
)
|
|
964
977
|
|
|
965
978
|
# Report a pass
|
|
@@ -968,7 +981,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
968
981
|
self.format_pbar_string(
|
|
969
982
|
TestReportingType.INTEGRATION,
|
|
970
983
|
f"{detection.name}:{test.name}",
|
|
971
|
-
FinalTestingStates.PASS
|
|
984
|
+
FinalTestingStates.PASS,
|
|
972
985
|
start_time=test_start_time,
|
|
973
986
|
set_pbar=False,
|
|
974
987
|
)
|
|
@@ -979,7 +992,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
979
992
|
self.format_pbar_string(
|
|
980
993
|
TestReportingType.INTEGRATION,
|
|
981
994
|
f"{detection.name}:{test.name}",
|
|
982
|
-
FinalTestingStates.SKIP
|
|
995
|
+
FinalTestingStates.SKIP,
|
|
983
996
|
start_time=test_start_time,
|
|
984
997
|
set_pbar=False,
|
|
985
998
|
)
|
|
@@ -990,7 +1003,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
990
1003
|
self.format_pbar_string(
|
|
991
1004
|
TestReportingType.INTEGRATION,
|
|
992
1005
|
f"{detection.name}:{test.name}",
|
|
993
|
-
FinalTestingStates.FAIL
|
|
1006
|
+
FinalTestingStates.FAIL,
|
|
994
1007
|
start_time=test_start_time,
|
|
995
1008
|
set_pbar=False,
|
|
996
1009
|
)
|
|
@@ -1001,7 +1014,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1001
1014
|
self.format_pbar_string(
|
|
1002
1015
|
TestReportingType.INTEGRATION,
|
|
1003
1016
|
f"{detection.name}:{test.name}",
|
|
1004
|
-
FinalTestingStates.ERROR
|
|
1017
|
+
FinalTestingStates.ERROR,
|
|
1005
1018
|
start_time=test_start_time,
|
|
1006
1019
|
set_pbar=False,
|
|
1007
1020
|
)
|
|
@@ -1028,7 +1041,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1028
1041
|
# check if the behavior is to always pause
|
|
1029
1042
|
if self.global_config.post_test_behavior == PostTestBehavior.always_pause:
|
|
1030
1043
|
return True
|
|
1031
|
-
elif
|
|
1044
|
+
elif (
|
|
1045
|
+
self.global_config.post_test_behavior
|
|
1046
|
+
== PostTestBehavior.pause_on_failure
|
|
1047
|
+
):
|
|
1032
1048
|
# If the behavior is to pause on failure, check for failure (either explicitly, or
|
|
1033
1049
|
# just a lack of a result)
|
|
1034
1050
|
if test.result is None or test.result.failed:
|
|
@@ -1053,15 +1069,15 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1053
1069
|
"""
|
|
1054
1070
|
# Get the start time and compute the timeout
|
|
1055
1071
|
search_start_time = time.time()
|
|
1056
|
-
search_stop_time = time.time() + self.sync_obj.timeout_seconds
|
|
1072
|
+
search_stop_time = time.time() + self.sync_obj.timeout_seconds
|
|
1057
1073
|
|
|
1058
1074
|
# Make a copy of the search string since we may
|
|
1059
1075
|
# need to make some small changes to it below
|
|
1060
1076
|
search = detection.search
|
|
1061
1077
|
|
|
1062
1078
|
# Ensure searches that do not begin with '|' must begin with 'search '
|
|
1063
|
-
if not search.strip().startswith("|"):
|
|
1064
|
-
if not search.strip().startswith("search "):
|
|
1079
|
+
if not search.strip().startswith("|"):
|
|
1080
|
+
if not search.strip().startswith("search "):
|
|
1065
1081
|
search = f"search {search}"
|
|
1066
1082
|
|
|
1067
1083
|
# exponential backoff for wait time
|
|
@@ -1069,7 +1085,6 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1069
1085
|
|
|
1070
1086
|
# Retry until timeout
|
|
1071
1087
|
while time.time() < search_stop_time:
|
|
1072
|
-
|
|
1073
1088
|
# This loop allows us to capture shutdown events without being
|
|
1074
1089
|
# stuck in an extended sleep. Remember that this raises an exception
|
|
1075
1090
|
for _ in range(pow(2, tick - 1)):
|
|
@@ -1077,8 +1092,8 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1077
1092
|
self.format_pbar_string(
|
|
1078
1093
|
TestReportingType.UNIT,
|
|
1079
1094
|
f"{detection.name}:{test.name}",
|
|
1080
|
-
TestingStates.PROCESSING
|
|
1081
|
-
start_time=start_time
|
|
1095
|
+
TestingStates.PROCESSING,
|
|
1096
|
+
start_time=start_time,
|
|
1082
1097
|
)
|
|
1083
1098
|
|
|
1084
1099
|
time.sleep(1)
|
|
@@ -1086,7 +1101,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1086
1101
|
self.format_pbar_string(
|
|
1087
1102
|
TestReportingType.UNIT,
|
|
1088
1103
|
f"{detection.name}:{test.name}",
|
|
1089
|
-
TestingStates.SEARCHING
|
|
1104
|
+
TestingStates.SEARCHING,
|
|
1090
1105
|
start_time=start_time,
|
|
1091
1106
|
)
|
|
1092
1107
|
|
|
@@ -1094,10 +1109,25 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1094
1109
|
job = self.get_conn().search(query=search, **kwargs)
|
|
1095
1110
|
results = JSONResultsReader(job.results(output_mode="json"))
|
|
1096
1111
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1112
|
+
if detection.rba is not None:
|
|
1113
|
+
risk_object_fields_set = set(
|
|
1114
|
+
[o.field for o in detection.rba.risk_objects]
|
|
1115
|
+
) # just the "Risk Objects"
|
|
1116
|
+
threat_object_fields_set = set(
|
|
1117
|
+
[o.field for o in detection.rba.threat_objects]
|
|
1118
|
+
) # just the "threat objects"
|
|
1119
|
+
else:
|
|
1120
|
+
# For some searches, like Hunting Searches, there should
|
|
1121
|
+
# not be any risk or threat objects.
|
|
1122
|
+
risk_object_fields_set: set[str] = (
|
|
1123
|
+
set()
|
|
1124
|
+
) # just the "Risk Objects" (of which there are none)
|
|
1125
|
+
threat_object_fields_set: set[str] = (
|
|
1126
|
+
set()
|
|
1127
|
+
) # just the "threat objects" (of which there are none)
|
|
1128
|
+
full_rba_field_set: set[str] = risk_object_fields_set.union(
|
|
1129
|
+
threat_object_fields_set
|
|
1130
|
+
)
|
|
1101
1131
|
|
|
1102
1132
|
# Ensure the search had at least one result
|
|
1103
1133
|
if int(job.content.get("resultCount", "0")) > 0:
|
|
@@ -1121,7 +1151,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1121
1151
|
missing_risk_objects = risk_object_fields_set - results_fields_set
|
|
1122
1152
|
if len(missing_risk_objects) > 0:
|
|
1123
1153
|
# Report a failure in such cases
|
|
1124
|
-
e = Exception(
|
|
1154
|
+
e = Exception(
|
|
1155
|
+
f"The risk object field(s) {missing_risk_objects} are missing in the "
|
|
1156
|
+
"detection results"
|
|
1157
|
+
)
|
|
1125
1158
|
test.result.set_job_content(
|
|
1126
1159
|
job.content,
|
|
1127
1160
|
self.infrastructure,
|
|
@@ -1130,17 +1163,21 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1130
1163
|
duration=time.time() - search_start_time,
|
|
1131
1164
|
)
|
|
1132
1165
|
|
|
1133
|
-
return
|
|
1166
|
+
return
|
|
1134
1167
|
|
|
1135
1168
|
# If we find one or more risk object fields that contain the string "null" then they were
|
|
1136
1169
|
# not populated and we should throw an error. This can happen if there is a typo
|
|
1137
1170
|
# on a field. In this case, the field will appear but will not contain any values
|
|
1138
1171
|
current_empty_fields: set[str] = set()
|
|
1139
1172
|
|
|
1140
|
-
for
|
|
1141
|
-
|
|
1173
|
+
# TODO (cmcginley): @ljstella is this something we're keeping for testing as
|
|
1174
|
+
# well?
|
|
1175
|
+
for field in full_rba_field_set:
|
|
1176
|
+
if result.get(field, "null") == "null":
|
|
1142
1177
|
if field in risk_object_fields_set:
|
|
1143
|
-
e = Exception(
|
|
1178
|
+
e = Exception(
|
|
1179
|
+
f"The risk object field {field} is missing in at least one result."
|
|
1180
|
+
)
|
|
1144
1181
|
test.result.set_job_content(
|
|
1145
1182
|
job.content,
|
|
1146
1183
|
self.infrastructure,
|
|
@@ -1171,7 +1208,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1171
1208
|
else:
|
|
1172
1209
|
empty_fields = empty_fields.union(current_empty_fields)
|
|
1173
1210
|
|
|
1174
|
-
missing_threat_objects =
|
|
1211
|
+
missing_threat_objects = (
|
|
1212
|
+
threat_object_fields_set - present_threat_objects
|
|
1213
|
+
)
|
|
1175
1214
|
# Report a failure if there were empty fields in a threat object in all results
|
|
1176
1215
|
if len(missing_threat_objects) > 0:
|
|
1177
1216
|
e = Exception(
|
|
@@ -1188,12 +1227,12 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1188
1227
|
return
|
|
1189
1228
|
|
|
1190
1229
|
test.result.set_job_content(
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
return
|
|
1230
|
+
job.content,
|
|
1231
|
+
self.infrastructure,
|
|
1232
|
+
TestResultStatus.PASS,
|
|
1233
|
+
duration=time.time() - search_start_time,
|
|
1234
|
+
)
|
|
1235
|
+
return
|
|
1197
1236
|
|
|
1198
1237
|
else:
|
|
1199
1238
|
# Report a failure if there were no results at all
|
|
@@ -1215,7 +1254,6 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1215
1254
|
splunk_search = f'search index="{index}" host="{host}" | delete'
|
|
1216
1255
|
kwargs = {"exec_mode": "blocking"}
|
|
1217
1256
|
try:
|
|
1218
|
-
|
|
1219
1257
|
job = self.get_conn().jobs.create(splunk_search, **kwargs)
|
|
1220
1258
|
results_stream = job.results(output_mode="json")
|
|
1221
1259
|
# TODO: should we be doing something w/ this reader?
|
|
@@ -1249,8 +1287,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1249
1287
|
# Before attempting to replay the file, ensure that the index we want
|
|
1250
1288
|
# to replay into actuall exists. If not, we should throw a detailed
|
|
1251
1289
|
# exception that can easily be interpreted by the user.
|
|
1252
|
-
if
|
|
1253
|
-
attack_data_file.custom_index not
|
|
1290
|
+
if (
|
|
1291
|
+
attack_data_file.custom_index is not None
|
|
1292
|
+
and attack_data_file.custom_index not in self.all_indexes_on_server
|
|
1293
|
+
):
|
|
1254
1294
|
raise ReplayIndexDoesNotExistOnServer(
|
|
1255
1295
|
f"Unable to replay data file {attack_data_file.data} "
|
|
1256
1296
|
f"into index '{attack_data_file.custom_index}'. "
|
|
@@ -1259,13 +1299,17 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1259
1299
|
)
|
|
1260
1300
|
|
|
1261
1301
|
tempfile = mktemp(dir=tmp_dir)
|
|
1262
|
-
if not (
|
|
1263
|
-
|
|
1302
|
+
if not (
|
|
1303
|
+
str(attack_data_file.data).startswith("http://")
|
|
1304
|
+
or str(attack_data_file.data).startswith("https://")
|
|
1305
|
+
):
|
|
1264
1306
|
if pathlib.Path(str(attack_data_file.data)).is_file():
|
|
1265
|
-
self.format_pbar_string(
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1307
|
+
self.format_pbar_string(
|
|
1308
|
+
TestReportingType.GROUP,
|
|
1309
|
+
test_group.name,
|
|
1310
|
+
"Copying Data",
|
|
1311
|
+
test_group_start_time,
|
|
1312
|
+
)
|
|
1269
1313
|
|
|
1270
1314
|
try:
|
|
1271
1315
|
copyfile(str(attack_data_file.data), tempfile)
|
|
@@ -1289,8 +1333,8 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1289
1333
|
self.format_pbar_string(
|
|
1290
1334
|
TestReportingType.GROUP,
|
|
1291
1335
|
test_group.name,
|
|
1292
|
-
TestingStates.DOWNLOADING
|
|
1293
|
-
start_time=test_group_start_time
|
|
1336
|
+
TestingStates.DOWNLOADING,
|
|
1337
|
+
start_time=test_group_start_time,
|
|
1294
1338
|
)
|
|
1295
1339
|
|
|
1296
1340
|
Utils.download_file_from_http(
|
|
@@ -1307,8 +1351,8 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1307
1351
|
self.format_pbar_string(
|
|
1308
1352
|
TestReportingType.GROUP,
|
|
1309
1353
|
test_group.name,
|
|
1310
|
-
TestingStates.REPLAYING
|
|
1311
|
-
start_time=test_group_start_time
|
|
1354
|
+
TestingStates.REPLAYING,
|
|
1355
|
+
start_time=test_group_start_time,
|
|
1312
1356
|
)
|
|
1313
1357
|
|
|
1314
1358
|
self.hec_raw_replay(tempfile, attack_data_file)
|
|
@@ -1392,7 +1436,6 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1392
1436
|
requested_acks = {"acks": [jsonResponse["ackId"]]}
|
|
1393
1437
|
while True:
|
|
1394
1438
|
try:
|
|
1395
|
-
|
|
1396
1439
|
res = requests.post(
|
|
1397
1440
|
url_with_hec_ack_path,
|
|
1398
1441
|
json=requested_acks,
|
|
@@ -1424,7 +1467,9 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
|
|
|
1424
1467
|
pass
|
|
1425
1468
|
|
|
1426
1469
|
def finish(self):
|
|
1427
|
-
self.pbar.bar_format =
|
|
1470
|
+
self.pbar.bar_format = (
|
|
1471
|
+
f"Finished running tests on instance: [{self.get_name()}]"
|
|
1472
|
+
)
|
|
1428
1473
|
self.pbar.update()
|
|
1429
1474
|
self.pbar.close()
|
|
1430
1475
|
|