contentctl 5.5.9__py3-none-any.whl → 5.5.11__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.
@@ -30,6 +30,7 @@ class Build:
30
30
  updated_conf_files.update(
31
31
  conf_output.writeDetections(input_dto.director_output_dto.detections)
32
32
  )
33
+ updated_conf_files.update(conf_output.writeFbds())
33
34
  updated_conf_files.update(
34
35
  conf_output.writeStories(input_dto.director_output_dto.stories)
35
36
  )
@@ -7,7 +7,14 @@ from functools import cached_property
7
7
  from typing import Any, Callable
8
8
 
9
9
  import splunklib.client as splunklib # type: ignore
10
- from pydantic import BaseModel, Field, PrivateAttr, computed_field
10
+ from pydantic import (
11
+ BaseModel,
12
+ Field,
13
+ PrivateAttr,
14
+ computed_field,
15
+ model_validator,
16
+ )
17
+ from semantic_version import Version
11
18
  from splunklib.binding import HTTPError, ResponseReader # type: ignore
12
19
  from splunklib.data import Record # type: ignore
13
20
 
@@ -21,6 +28,58 @@ ENABLE_LOGGING = False
21
28
  LOG_LEVEL = logging.DEBUG
22
29
  LOG_PATH = "content_versioning_service.log"
23
30
 
31
+ # The app name of ES; needed to check ES version
32
+ ES_APP_NAME = "SplunkEnterpriseSecuritySuite"
33
+
34
+
35
+ class CMSEvent(BaseModel):
36
+ """
37
+ A model representing a CMS event. This is used to validate that detections have been installed
38
+ in a way that is compatible with content versioning.
39
+ """
40
+
41
+ content: str # JSON string
42
+
43
+ # The app name of the detection
44
+ app_name: str
45
+
46
+ # The detection id of the detection
47
+ detection_id: str
48
+
49
+ # The version of the detection
50
+ version: str
51
+
52
+ # The saved search name of the detection
53
+ action_correlationsearch_label: str
54
+
55
+ @model_validator(mode="before")
56
+ @classmethod
57
+ def extract_from_content(cls, data):
58
+ """Extract fields from content JSON if not already provided"""
59
+ if isinstance(data, dict) and "content" in data:
60
+ try:
61
+ content_str = data.get("content")
62
+ parsed = json.loads(content_str)
63
+
64
+ # Extract metadata fields - note the key has dots in it
65
+ metadata_str = parsed.get("action.correlationsearch.metadata", {})
66
+ metadata = (
67
+ json.loads(metadata_str)
68
+ if isinstance(metadata_str, str)
69
+ else metadata_str
70
+ )
71
+ data.setdefault("app_name", metadata.get("app_name"))
72
+ data.setdefault("detection_id", metadata.get("detection_id"))
73
+ data.setdefault("version", metadata.get("version"))
74
+ data.setdefault(
75
+ "action_correlationsearch_label",
76
+ parsed.get("action.correlationsearch.label"),
77
+ )
78
+ except (json.JSONDecodeError, AttributeError, KeyError, TypeError):
79
+ # If parsing fails, let Pydantic handle validation errors
80
+ raise ValueError("Failed to parse content JSON {}".format(data))
81
+ return data
82
+
24
83
 
25
84
  class ContentVersioningService(BaseModel):
26
85
  """
@@ -77,6 +136,47 @@ class ContentVersioningService(BaseModel):
77
136
  (self.validate_content_against_cms, "Validating Against CMS"),
78
137
  ]
79
138
 
139
+ @cached_property
140
+ def es_version(self) -> Version | None:
141
+ """
142
+ Returns the version of Enterprise Security installed on the instance; None if not installed.
143
+
144
+ :return: the version of ES, as a semver aware object
145
+ :rtype: :class:`semantic_version.Version`
146
+ """
147
+ if ES_APP_NAME not in self.service.apps:
148
+ return None
149
+ return Version(self.service.apps[ES_APP_NAME]["version"]) # type: ignore
150
+
151
+ @cached_property
152
+ def kvstore_content_versioning(self) -> bool:
153
+ """
154
+ Indicates whether we should test content versioning based on kvstore logic. Content versioning
155
+ should be tested with kvstore logic when ES is at least version 8.3.0.
156
+
157
+ :return: a bool indicating whether we should test content versioning with kvstore logic
158
+ :rtype: bool
159
+ """
160
+ es_version = self.es_version
161
+ return es_version is not None and es_version >= Version("8.3.0")
162
+
163
+ @cached_property
164
+ def indexbased_content_versioning(self) -> bool:
165
+ """
166
+ Indicates whether we should test content versioning based on indexbased logic. Content versioning
167
+ should be tested with indexbased logic when ES is less than version 8.3.0 but greater than or equal
168
+ to version 8.0.0.
169
+
170
+ :return: a bool indicating whether we should test content versioning with indexbased logic
171
+ :rtype: bool
172
+ """
173
+ es_version = self.es_version
174
+ return (
175
+ es_version is not None
176
+ and es_version >= Version("8.0.0")
177
+ and es_version < Version("8.3.0")
178
+ )
179
+
80
180
  def _query_content_versioning_service(
81
181
  self, method: str, body: dict[str, Any] = {}
82
182
  ) -> Record:
@@ -97,6 +197,17 @@ class ContentVersioningService(BaseModel):
97
197
 
98
198
  # Query the content versioning service
99
199
  try:
200
+ # TODO: The comment out section is for validating versioning is enabled and ready to go. The validation
201
+ # workflow (whether to be part of wait_for_cms_main or a separate function) is planed to be implemented
202
+ # in later contentctl-ng.
203
+ # API endpoint for checking versioning status after ES 8.3.0
204
+ # if method == "GET" and self.kvstore_content_versioning:
205
+ # response = self.service.request(
206
+ # method=method,
207
+ # path_segment="content_versioning/versioning_apps",
208
+ # app="SA-ContentVersioning",
209
+ # )
210
+ # if self.indexbased_content_versioning:
100
211
  response = self.service.request( # type: ignore
101
212
  method=method,
102
213
  path_segment="configs/conf-feature_flags/general",
@@ -141,6 +252,27 @@ class ContentVersioningService(BaseModel):
141
252
 
142
253
  # Find the versioning_activated field and report any errors
143
254
  try:
255
+ # TODO: The comment out section is for validating versioning is enabled and ready to go. The validation
256
+ # workflow (whether to be part of wait_for_cms_main or a separate function) is planed to be implemented
257
+ # in later contentctl-ng.
258
+ # Validating response by checking `status` field in `DA-ESS-ContentUpdate` app
259
+ # if self.kvstore_content_versioning:
260
+ # if "content" in data:
261
+ # for app in data["content"]:
262
+ # if app.get("name") == "DA-ESS-ContentUpdate":
263
+ # # If there is error message versioning is not activated properly
264
+ # if "message" in app:
265
+ # return False
266
+
267
+ # # If the installed verion is not the same as the test version
268
+ # if app.get("version") != self.global_config.app.version:
269
+ # return False
270
+
271
+ # if app.get("status") == "active":
272
+ # return True
273
+ # else:
274
+ # return False
275
+ # if self.indexbased_content_versioning:
144
276
  for entry in data["entry"]:
145
277
  if entry["name"] == "general":
146
278
  return bool(int(entry["content"]["versioning_activated"]))
@@ -163,6 +295,18 @@ class ContentVersioningService(BaseModel):
163
295
  method="POST", body={"versioning_activated": True}
164
296
  )
165
297
 
298
+ # TODO: The comment out section is for validating versioning is enabled and ready to go. The validation
299
+ # workflow (whether to be part of wait_for_cms_main or a separate function) is planed to be implemented
300
+ # in later contentctl-ng.
301
+ # The versioning is expected to be ready within 10 minutes
302
+ # if self.kvstore_content_versioning:
303
+ # timeout = 600
304
+ # while not self.is_versioning_activated:
305
+ # time.sleep(60)
306
+ # timeout -= 60
307
+ # if timeout <= 0:
308
+ # break
309
+
166
310
  # Confirm versioning has been enabled
167
311
  if not self.is_versioning_activated:
168
312
  raise Exception(
@@ -173,23 +317,6 @@ class ContentVersioningService(BaseModel):
173
317
  f"[{self.infrastructure.instance_name}] Versioning service successfully activated"
174
318
  )
175
319
 
176
- @computed_field
177
- @cached_property
178
- def cms_fields(self) -> list[str]:
179
- """
180
- Property listing the fields we want to pull from the cms_main index
181
-
182
- :returns: a list of strings, the fields we want
183
- :rtype: list[str]
184
- """
185
- return [
186
- "app_name",
187
- "detection_id",
188
- "version",
189
- "action.correlationsearch.label",
190
- "sourcetype",
191
- ]
192
-
193
320
  @property
194
321
  def is_cms_parser_enabled(self) -> bool:
195
322
  """
@@ -292,16 +419,37 @@ class ContentVersioningService(BaseModel):
292
419
  )
293
420
 
294
421
  # Construct the query looking for CMS events matching the content app name
295
- query = (
296
- f"search index=cms_main sourcetype=stash_common_detection_model "
297
- f'app_name="{self.global_config.app.appid}" | fields {", ".join(self.cms_fields)}'
298
- )
422
+ if self.kvstore_content_versioning:
423
+ query = (
424
+ f"| inputlookup cms_content_lookup | search app_name={self.global_config.app.appid}"
425
+ f"| fields content"
426
+ )
427
+ elif self.indexbased_content_versioning:
428
+ query = (
429
+ f"search index=cms_main sourcetype=stash_common_detection_model "
430
+ f'app_name="{self.global_config.app.appid}" | fields _raw'
431
+ )
432
+ else:
433
+ if self.kvstore_content_versioning:
434
+ raise Exception(
435
+ f"Unable to perform search to cms_content_lookup in ES version {self.es_version}"
436
+ )
437
+ elif self.indexbased_content_versioning:
438
+ raise Exception(
439
+ f"Unable to perform search to cms_main index in ES version {self.es_version}"
440
+ )
441
+ else:
442
+ raise Exception(
443
+ f"Unable to determine content versioning method for ES version {self.es_version}. "
444
+ "Expected ES version >= 8.0.0."
445
+ )
299
446
  self.logger.debug(
300
447
  f"[{self.infrastructure.instance_name}] Query on cms_main: {query}"
301
448
  )
302
449
 
303
450
  # Get the job as a blocking operation, set the cache, and return
304
451
  self._cms_main_job = self.service.search(query, exec_mode="blocking") # type: ignore
452
+
305
453
  return self._cms_main_job
306
454
 
307
455
  def get_num_cms_events(self, use_cache: bool = False) -> int:
@@ -375,8 +523,13 @@ class ContentVersioningService(BaseModel):
375
523
  # Increment the offset for each result
376
524
  offset += 1
377
525
 
526
+ if self.kvstore_content_versioning:
527
+ cms_event = CMSEvent(content=cms_event["content"])
528
+ elif self.indexbased_content_versioning:
529
+ cms_event = CMSEvent(content=cms_event["_raw"])
530
+
378
531
  # Get the name of the search in the CMS event
379
- cms_entry_name = cms_event["action.correlationsearch.label"]
532
+ cms_entry_name = cms_event.action_correlationsearch_label
380
533
  self.logger.info(
381
534
  f"[{self.infrastructure.instance_name}] {offset}: Matching cms_main entry "
382
535
  f"'{cms_entry_name}' against detections"
@@ -456,14 +609,14 @@ class ContentVersioningService(BaseModel):
456
609
  )
457
610
 
458
611
  def validate_detection_against_cms_event(
459
- self, cms_event: dict[str, Any], detection: Detection
612
+ self, cms_event: CMSEvent, detection: Detection
460
613
  ) -> Exception | None:
461
614
  """
462
615
  Given an event from the cms_main index and the matched detection, compare fields and look
463
616
  for any inconsistencies
464
617
 
465
618
  :param cms_event: The event from the cms_main index
466
- :type cms_event: dict[str, Any]
619
+ :type cms_event: CMSEvent
467
620
  :param detection: The matched detection
468
621
  :type detection: :class:`contentctl.objects.detection.Detection`
469
622
 
@@ -472,17 +625,18 @@ class ContentVersioningService(BaseModel):
472
625
  """
473
626
  # TODO (PEX-509): validate additional fields between the cms_event and the detection
474
627
 
475
- cms_uuid = uuid.UUID(cms_event["detection_id"])
628
+ cms_uuid = uuid.UUID(cms_event.detection_id)
476
629
  rule_name_from_detection = detection.get_action_dot_correlationsearch_dot_label(
477
630
  self.global_config.app
478
631
  )
479
632
 
633
+ cms_entry_name = cms_event.action_correlationsearch_label
634
+
480
635
  # Compare the correlation search label
481
- if cms_event["action.correlationsearch.label"] != rule_name_from_detection:
636
+ if cms_entry_name != rule_name_from_detection:
482
637
  msg = (
483
638
  f"[{self.infrastructure.instance_name}][{detection.name}]: Correlation search "
484
- f"label in cms_event ('{cms_event['action.correlationsearch.label']}') does not "
485
- "match detection name"
639
+ f"label in cms_event ('{cms_entry_name}') does not match detection name"
486
640
  )
487
641
  self.logger.error(msg)
488
642
  return Exception(msg)
@@ -494,12 +648,12 @@ class ContentVersioningService(BaseModel):
494
648
  )
495
649
  self.logger.error(msg)
496
650
  return Exception(msg)
497
- elif cms_event["version"] != f"{detection.version}.1":
651
+ elif cms_event.version != f"{detection.version}.1":
498
652
  # Compare the versions (we append '.1' to the detection version to be in line w/ the
499
653
  # internal representation in ES)
500
654
  msg = (
501
655
  f"[{self.infrastructure.instance_name}] [{detection.name}]: Version in cms_event "
502
- f"('{cms_event['version']}') does not match version in detection "
656
+ f"('{cms_event.version}') does not match version in detection "
503
657
  f"('{detection.version}.1')"
504
658
  )
505
659
  self.logger.error(msg)
@@ -91,8 +91,8 @@ class ScheduleConfig(StrEnum):
91
91
  Configuraton values for the saved search schedule
92
92
  """
93
93
 
94
- EARLIEST_TIME = "-5y@y"
95
- LATEST_TIME = "-1m@m"
94
+ EARLIEST_TIME = "0"
95
+ LATEST_TIME = "now"
96
96
  CRON_SCHEDULE = "0 0 1 1 *"
97
97
 
98
98
 
@@ -110,6 +110,67 @@ class ConfOutput:
110
110
  )
111
111
  return written_files
112
112
 
113
+ def writeFbds(self) -> set[pathlib.Path]:
114
+ written_files: set[pathlib.Path] = set()
115
+
116
+ # Path to the static FBD configuration file
117
+ fbd_config_path = self.config.path / "static_configs" / "savedsearches_fbd.conf"
118
+
119
+ if not fbd_config_path.exists():
120
+ # If the file doesn't exist, just return empty set - no FBDs to write
121
+ print("No FBD configuration file found; skipping FBD writing to conf.")
122
+ return written_files
123
+ print(f"Reading FBD configuration from {fbd_config_path}")
124
+
125
+ # Read and parse the FBD configuration file
126
+ fbd_stanzas = self._parse_fbd_config_file(fbd_config_path)
127
+
128
+ if fbd_stanzas: # Only write if there are actual stanzas
129
+ written_files.add(
130
+ ConfWriter.writeConfFile(
131
+ pathlib.Path("default/savedsearches.conf"),
132
+ "savedsearches_fbds.j2",
133
+ self.config,
134
+ fbd_stanzas,
135
+ )
136
+ )
137
+
138
+ return written_files
139
+
140
+ def _parse_fbd_config_file(self, file_path: pathlib.Path) -> list:
141
+ """Parse the FBD configuration file into individual stanza objects."""
142
+ stanzas = []
143
+ current_stanza_lines = []
144
+
145
+ with open(file_path, "r", encoding="utf-8") as f:
146
+ for line in f:
147
+ stripped_line = line.strip()
148
+
149
+ # Skip comment lines (lines starting with # after stripping whitespace)
150
+ if stripped_line.startswith("#"):
151
+ continue
152
+
153
+ # If we hit a blank line and have accumulated stanza content, finalize the current stanza
154
+ if not stripped_line:
155
+ if current_stanza_lines:
156
+ stanza_content = "\n".join(current_stanza_lines)
157
+ stanza_obj = type(
158
+ "FbdStanza", (), {"content": stanza_content}
159
+ )()
160
+ stanzas.append(stanza_obj)
161
+ current_stanza_lines = []
162
+ continue
163
+
164
+ # Accumulate non-empty, non-comment lines for the current stanza
165
+ current_stanza_lines.append(line.rstrip())
166
+ # Handle the last stanza if the file doesn't end with a blank line
167
+ if current_stanza_lines:
168
+ stanza_content = "\n".join(current_stanza_lines)
169
+ stanza_obj = type("FbdStanza", (), {"content": stanza_content})()
170
+ stanzas.append(stanza_obj)
171
+
172
+ return stanzas
173
+
113
174
  def writeStories(self, objects: list[Story]) -> set[pathlib.Path]:
114
175
  written_files: set[pathlib.Path] = set()
115
176
  written_files.add(
@@ -1,10 +1,15 @@
1
1
  ### {{app.label}} DETECTIONS ###
2
2
 
3
+ [default]
4
+ disabled = 1
5
+ description = "This search was removed in a previous release, or is otherwise not present."
6
+ search = | makeresults | eval text = "This search was removed in a previous release, or is otherwise not present."
7
+
3
8
  {% for detection in objects %}
4
9
  [{{ detection.get_conf_stanza_name(app) }}]
5
10
  action.escu = 0
6
11
  action.escu.enabled = 1
7
- description = {{ detection.status_aware_description | escapeNewlines() }}
12
+ description = {{ detection.status_aware_description | escapeNewlines() }}
8
13
  action.escu.mappings = {{ detection.mappings | tojson }}
9
14
  action.escu.data_models = {{ detection.datamodel | tojson }}
10
15
  action.escu.eli5 = {{ detection.status_aware_description | escapeNewlines() }}
@@ -0,0 +1,9 @@
1
+
2
+
3
+ ### {{app.label}} FBDS ###
4
+
5
+ {% for fbd_stanza in objects %}
6
+ {{ fbd_stanza.content }}
7
+
8
+ {% endfor %}
9
+ ### END {{app.label}} FBDS ###
@@ -6,18 +6,9 @@ export = system
6
6
  [savedsearches]
7
7
  owner = admin
8
8
 
9
- ## Correlation Searches
10
- [correlationsearches]
11
- access = read : [ * ], write : [ * ]
12
-
13
- [governance]
14
- access = read : [ * ], write : [ * ]
15
-
16
- ## Managed Configurations
17
- [managed_configurations]
18
- access = read : [ * ], write : [ * ]
19
-
20
- ## Postprocess
21
- [postprocess]
22
- access = read : [ * ], write : [ * ]
23
-
9
+ ## DO NOT EXPORT THE [default] stanza, and the [default] stanza alone.
10
+ ## Because this comes later in the default.meta file, it overrides the
11
+ ## export = system for [] above.
12
+ ## We MAY want to consider change the access, like making this stanza read-only or similar
13
+ [savedsearches/default]
14
+ export = none
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contentctl
3
- Version: 5.5.9
3
+ Version: 5.5.11
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  License-File: LICENSE.md
@@ -1,5 +1,5 @@
1
1
  contentctl/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
- contentctl/actions/build.py,sha256=C95i2MhxHkNJb9IdVp_MnRtuoowXxc5Gu-0xK24JEeU,4036
2
+ contentctl/actions/build.py,sha256=q4TakZm1Kcurvr9yIrZ54VxNOmNSgJoVK5I8q1Iu2Qk,4099
3
3
  contentctl/actions/deploy_acs.py,sha256=TtDIrpZm3mMNpbs8yaritnS-kLPnOtJ2T2M9r2QwKL0,3263
4
4
  contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=zdJM_4LP31PlexLdoaKLf_XBrVFoGBwpVm566uAO1d4,10965
5
5
  contentctl/actions/detection_testing/GitService.py,sha256=sp2ZyV_gpyTjonh48obNzbCC1LDni8F2nN7ZPn2LqnY,11333
@@ -44,8 +44,8 @@ contentctl/objects/baseline.py,sha256=EMcuz_9sVgOFh3YCj871GSAA6v3FIkRTf90-LAHq-J
44
44
  contentctl/objects/baseline_tags.py,sha256=SkGlsfigaARss3itHOgWnKhRDEB6NX8bMhfovrBUmhk,1609
45
45
  contentctl/objects/config.py,sha256=la0mUk1183ZD0gav7bGekhJxj4AOjn8hF5p7jwNqdhM,57938
46
46
  contentctl/objects/constants.py,sha256=VwwQtJBGC_zb3ukjb3A7P0CwAlyhacWiXczwAW5Jiog,5466
47
- contentctl/objects/content_versioning_service.py,sha256=BDk_TV1PTVoXpPcUxqTLa5_bjkfOs9PFYgqTuzOS9UI,20566
48
- contentctl/objects/correlation_search.py,sha256=rcgO0jIJgSFFOR3JXeQLwab4RZuChQOYhoGDurcHXkQ,51062
47
+ contentctl/objects/content_versioning_service.py,sha256=KZs0DRo5AX6TzVIUDvlTiYKaKB6nZqx1WPko9-JLqP0,27341
48
+ contentctl/objects/correlation_search.py,sha256=cTFpdcBXmQ9AhOkNK2EK4xOafsIRktsJKNVy4e1WAns,51056
49
49
  contentctl/objects/dashboard.py,sha256=wdNCIC1MExvpsB_EyPY9ZDo9Xu9V5WDI6wkunW0fTdk,4995
50
50
  contentctl/objects/data_source.py,sha256=O58GArXVlflz3dCtVOn96Ubyi5_ekSC1N9LuveQNws4,2019
51
51
  contentctl/objects/deployment.py,sha256=OctNayxFPRvrQtTklAKgfjCXFKOspD19swLj0hi6dWE,3323
@@ -94,7 +94,7 @@ contentctl/objects/unit_test_result.py,sha256=69VEAmajeRnWIdl6Xc90LU6hboEjJI9szF
94
94
  contentctl/output/api_json_output.py,sha256=AwuXFVzg3bY0DUsYaEGM73LAr9mJ5nxkOmUdVJgTzRs,8563
95
95
  contentctl/output/attack_nav_output.py,sha256=-zK9zxBFWQooLjfLeCJaKARemA1BhoiEYLYYT2Or9PQ,7088
96
96
  contentctl/output/attack_nav_writer.py,sha256=AiQU3q8hzz_lJECI-sjyqOsWx64HUugg3aAHEeZl-qM,2750
97
- contentctl/output/conf_output.py,sha256=eoDbqadndEnURr0IpY7cYVsEAkKPaaxg6cuHvn3I0Nw,11039
97
+ contentctl/output/conf_output.py,sha256=lWEGKETO-d59_UyNEMqvXiYi92Mm5zn37RzywLdQNJ8,13575
98
98
  contentctl/output/conf_writer.py,sha256=3bo_VIFH3Qd96LLaYFJ1uwYQQW0UFPB0Rf3Bs9_N2W4,15110
99
99
  contentctl/output/doc_md_output.py,sha256=jHOiuO8IWQ4adR0ekj-Af0JI9XhOGI-zYfKqV34f5Vw,3551
100
100
  contentctl/output/jinja_writer.py,sha256=k3yVRnz8KnboodME09oTHkP_54uF-YUGiKuJHZ9XTxo,1205
@@ -124,7 +124,8 @@ contentctl/output/templates/header.j2,sha256=3usV7jm1q6J-QNnQrZzII9cN0XEGQjg_eVK
124
124
  contentctl/output/templates/macros.j2,sha256=SLcQQ5X7TZS8j-2qP06BTXqdIcnwoYqTAaBLX2Dge7Y,390
125
125
  contentctl/output/templates/panel.j2,sha256=Cw_W6p-14n6UivVfpS75KKJiJ2VpdGsSBceYsUYe9gk,221
126
126
  contentctl/output/templates/savedsearches_baselines.j2,sha256=WHZB4e0vmeym8832VxRmuUfDJ-YRYt6emcYaJrghI58,1709
127
- contentctl/output/templates/savedsearches_detections.j2,sha256=xb4G7ikTD89cK7FF2TxwuFO1t9AQ43-DmT2LaemUMP4,5326
127
+ contentctl/output/templates/savedsearches_detections.j2,sha256=wMLRPr_N3PfPnp0rC7buKNGWcX2N6ulAAtD4SnDuq8M,5556
128
+ contentctl/output/templates/savedsearches_fbds.j2,sha256=iYjuEESKzSVyornnBfcR_JPafHHAlff_G2LeXQRGMzc,132
128
129
  contentctl/output/templates/savedsearches_investigations.j2,sha256=KH2r8SgyAMiettSHypSbA2-1XmQ_8_8xzk3BkbZ1Re4,1196
129
130
  contentctl/output/templates/server.conf.j2,sha256=sPZUkiuJNGm9R8rpjfRKyuAvmmQb0C4w9Q6hpmvmPeU,127
130
131
  contentctl/output/templates/transforms.j2,sha256=TEKZi8DWpcCysRTNvuLEgAwx-g1SZ2E0CkLiu6v6AlU,1339
@@ -143,7 +144,7 @@ contentctl/templates/app_template/default/data/ui/views/escu_summary.xml,sha256=
143
144
  contentctl/templates/app_template/default/data/ui/views/feedback.xml,sha256=uM71EMK2uFz8h68nOTNKGnYxob3HhE_caSL6yA-3H-k,696
144
145
  contentctl/templates/app_template/default/use_case_library.conf,sha256=zWuCOOl8SiP7Kit2s-de4KRu3HySLtBSXcp1QnJx0ec,168
145
146
  contentctl/templates/app_template/lookups/mitre_enrichment.csv,sha256=tifPQjFoQHtvpb78hxSP2fKHnHeehNbZDwUjdvc0aEM,66072
146
- contentctl/templates/app_template/metadata/default.meta,sha256=h66ea1l3qMzDRgDUAXsJvGKeJnp5w-s2unYMZ9dJLzM,433
147
+ contentctl/templates/app_template/metadata/default.meta,sha256=JUcThUfajDTW3ZrybyD9ILCThnNIRTcoezdFzpRZV-c,446
147
148
  contentctl/templates/app_template/static/appIcon.png,sha256=jcJ1PNdkBX7Kl_y9Tf0SZ55OJYA2PpwjvkVvBt9_OoE,3658
148
149
  contentctl/templates/app_template/static/appIconAlt.png,sha256=uRXjoHQQjs0-BxcK-3KNBEdck1adDNTHMvV14xR4W0g,2656
149
150
  contentctl/templates/app_template/static/appIconAlt_2x.png,sha256=I0m-CPRqq7ak9NJQZGGmz6Ac4pmzFV_SonOUxOEDOFs,7442
@@ -164,8 +165,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
164
165
  contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
165
166
  contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
166
167
  contentctl/templates/stories/cobalt_strike.yml,sha256=uj8idtDNOAIqpZ9p8usQg6mop1CQkJ5TlB4Q7CJdTIE,3082
167
- contentctl-5.5.9.dist-info/METADATA,sha256=8ZwdE3ebeS0SOmQfVLWKQnkxVfhCfnS24PAYVNaAiKI,5143
168
- contentctl-5.5.9.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
169
- contentctl-5.5.9.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
- contentctl-5.5.9.dist-info/licenses/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
171
- contentctl-5.5.9.dist-info/RECORD,,
168
+ contentctl-5.5.11.dist-info/METADATA,sha256=B4PK_gn_ci0WmHDnWtmGPitFvMSToPqn2Qy5lWcw72Q,5144
169
+ contentctl-5.5.11.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
170
+ contentctl-5.5.11.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
171
+ contentctl-5.5.11.dist-info/licenses/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
172
+ contentctl-5.5.11.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any