contentctl 4.1.3__py3-none-any.whl → 4.1.5__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.
@@ -111,11 +111,19 @@ class GitService(BaseModel):
111
111
  raise Exception(f"More than 1 Lookup reference the modified CSV file '{decoded_path}': {[l.file_path for l in matched ]}")
112
112
  else:
113
113
  updatedLookup = matched[0]
114
+ elif decoded_path.suffix == ".mlmodel":
115
+ # Detected a changed .mlmodel file. However, since we do not have testing for these detections at
116
+ # this time, we will ignore this change.
117
+ updatedLookup = None
118
+
119
+
114
120
  else:
115
- raise Exception(f"Error getting lookup object for file {str(decoded_path)}")
121
+ raise Exception(f"Detected a changed file in the lookups/ directory '{str(decoded_path)}'.\n"
122
+ "Only files ending in .csv, .yml, or .mlmodel are supported in this "
123
+ "directory. This file must be removed from the lookups/ directory.")
116
124
 
117
- if updatedLookup not in updated_lookups:
118
- # It is possible that both th CSV and YML have been modified for the same lookup,
125
+ if updatedLookup is not None and updatedLookup not in updated_lookups:
126
+ # It is possible that both the CSV and YML have been modified for the same lookup,
119
127
  # and we do not want to add it twice.
120
128
  updated_lookups.append(updatedLookup)
121
129
 
@@ -32,7 +32,6 @@ class NewContent:
32
32
  answers['status'] = "production" #start everything as production since that's what we INTEND the content to become
33
33
  answers['description'] = 'UPDATE_DESCRIPTION'
34
34
  file_name = answers['name'].replace(' ', '_').replace('-','_').replace('.','_').replace('/','_').lower()
35
- answers['kind'] = answers['detection_kind']
36
35
  answers['search'] = answers['detection_search'] + ' | `' + file_name + '_filter`'
37
36
  del answers['detection_search']
38
37
  answers['how_to_implement'] = 'UPDATE_HOW_TO_IMPLEMENT'
@@ -1,20 +1,11 @@
1
- import sys
2
1
 
3
- from dataclasses import dataclass
4
-
5
- from pydantic import ValidationError
6
- from typing import Union
7
-
8
- from contentctl.objects.enums import SecurityContentProduct
9
- from contentctl.objects.abstract_security_content_objects.security_content_object_abstract import (
10
- SecurityContentObject_Abstract,
11
- )
2
+ import pathlib
12
3
  from contentctl.input.director import Director, DirectorOutputDto
13
-
14
4
  from contentctl.objects.config import validate
15
5
  from contentctl.enrichments.attack_enrichment import AttackEnrichment
16
6
  from contentctl.enrichments.cve_enrichment import CveEnrichment
17
7
  from contentctl.objects.atomic import AtomicTest
8
+ from contentctl.helper.utils import Utils
18
9
 
19
10
 
20
11
  class Validate:
@@ -42,38 +33,44 @@ class Validate:
42
33
 
43
34
  director = Director(director_output_dto)
44
35
  director.execute(input_dto)
36
+ self.ensure_no_orphaned_files_in_lookups(input_dto.path, director_output_dto)
45
37
  return director_output_dto
46
38
 
47
- def validate_duplicate_uuids(
48
- self, security_content_objects: list[SecurityContentObject_Abstract]
49
- ):
50
- all_uuids = set()
51
- duplicate_uuids = set()
52
- for elem in security_content_objects:
53
- if elem.id in all_uuids:
54
- # The uuid has been found more than once
55
- duplicate_uuids.add(elem.id)
56
- else:
57
- # This is the first time the uuid has been found
58
- all_uuids.add(elem.id)
39
+
40
+ def ensure_no_orphaned_files_in_lookups(self, repo_path:pathlib.Path, director_output_dto:DirectorOutputDto):
41
+ """
42
+ This function ensures that only files which are relevant to lookups are included in the lookups folder.
43
+ This means that a file must be either:
44
+ 1. A lookup YML (.yml)
45
+ 2. A lookup CSV (.csv) which is referenced by a YML
46
+ 3. A lookup MLMODEL (.mlmodel) which is referenced by a YML.
47
+
48
+ All other files, includes CSV and MLMODEL files which are NOT
49
+ referenced by a YML, will generate an exception from this function.
50
+
51
+ Args:
52
+ repo_path (pathlib.Path): path to the root of the app
53
+ director_output_dto (DirectorOutputDto): director object with all constructed content
59
54
 
60
- if len(duplicate_uuids) == 0:
61
- return
55
+ Raises:
56
+ Exception: An Exception will be raised if there are any non .yml, .csv, or .mlmodel
57
+ files in this directory. Additionally, an exception will be raised if there
58
+ exists one or more .csv or .mlmodel files that are not referenced by at least 1
59
+ detection .yml file in this directory.
60
+ This avoids having additional, unused files in this directory that may be copied into
61
+ the app when it is built (which can cause appinspect errors or larger app size.)
62
+ """
63
+ lookupsDirectory = repo_path/"lookups"
64
+
65
+ # Get all of the files referneced by Lookups
66
+ usedLookupFiles:list[pathlib.Path] = [lookup.filename for lookup in director_output_dto.lookups if lookup.filename is not None] + [lookup.file_path for lookup in director_output_dto.lookups if lookup.file_path is not None]
62
67
 
63
- # At least once duplicate uuid has been found. Enumerate all
64
- # the pieces of content that use duplicate uuids
65
- duplicate_messages = []
66
- for uuid in duplicate_uuids:
67
- duplicate_uuid_content = [
68
- str(content.file_path)
69
- for content in security_content_objects
70
- if content.id in duplicate_uuids
71
- ]
72
- duplicate_messages.append(
73
- f"Duplicate UUID [{uuid}] in {duplicate_uuid_content}"
74
- )
75
-
76
- raise ValueError(
77
- "ERROR: Duplicate ID(s) found in objects:\n"
78
- + "\n - ".join(duplicate_messages)
79
- )
68
+ # Get all of the mlmodel and csv files in the lookups directory
69
+ csvAndMlmodelFiles = Utils.get_security_content_files_from_directory(lookupsDirectory, allowedFileExtensions=[".yml",".csv",".mlmodel"], fileExtensionsToReturn=[".csv",".mlmodel"])
70
+
71
+ # Generate an exception of any csv or mlmodel files exist but are not used
72
+ unusedLookupFiles:list[pathlib.Path] = [testFile for testFile in csvAndMlmodelFiles if testFile not in usedLookupFiles]
73
+ if len(unusedLookupFiles) > 0:
74
+ raise Exception(f"The following .csv or .mlmodel files exist in '{lookupsDirectory}', but are not referenced by a lookup file: {[str(path) for path in unusedLookupFiles]}")
75
+ return
76
+
@@ -34,6 +34,49 @@ class Utils:
34
34
  listOfFiles.append(pathlib.Path(os.path.join(dirpath, file)))
35
35
 
36
36
  return sorted(listOfFiles)
37
+
38
+ @staticmethod
39
+ def get_security_content_files_from_directory(path: pathlib.Path, allowedFileExtensions:list[str]=[".yml"], fileExtensionsToReturn:list[str]=[".yml"]) -> list[pathlib.Path]:
40
+
41
+ """
42
+ Get all of the Security Content Object Files rooted in a given directory. These will almost
43
+ certain be YML files, but could be other file types as specified by the user
44
+
45
+ Args:
46
+ path (pathlib.Path): The root path at which to enumerate all Security Content Files. All directories will be traversed.
47
+ allowedFileExtensions (set[str], optional): File extensions which are allowed to be present in this directory. In most cases, we do not want to allow the presence of non-YML files. Defaults to [".yml"].
48
+ fileExtensionsToReturn (set[str], optional): Filenames with extensions that should be returned from this function. For example, the lookups/ directory contains YML, CSV, and MLMODEL directories, but only the YMLs are Security Content Objects for constructing Lookyps. Defaults to[".yml"].
49
+
50
+ Raises:
51
+ Exception: Will raise an exception if allowedFileExtensions is not a subset of fileExtensionsToReturn.
52
+ Exception: Will raise an exception if the path passed to the function does not exist or is not a directory
53
+ Exception: Will raise an exception if there are any files rooted in the directory which are not in allowedFileExtensions
54
+
55
+ Returns:
56
+ list[pathlib.Path]: list of files with an extension in fileExtensionsToReturn found in path
57
+ """
58
+ if not set(fileExtensionsToReturn).issubset(set(allowedFileExtensions)):
59
+ raise Exception(f"allowedFileExtensions {allowedFileExtensions} MUST be a subset of fileExtensionsToReturn {fileExtensionsToReturn}, but it is not")
60
+
61
+ if not path.exists() or not path.is_dir():
62
+ raise Exception(f"Unable to get security_content files, required directory '{str(path)}' does not exist or is not a directory")
63
+
64
+ allowedFiles:list[pathlib.Path] = []
65
+ erroneousFiles:list[pathlib.Path] = []
66
+ #Get every single file extension
67
+ for filePath in path.glob("**/*.*"):
68
+ if filePath.suffix in allowedFileExtensions:
69
+ # Yes these are allowed
70
+ allowedFiles.append(filePath)
71
+ else:
72
+ # No these have not been allowed
73
+ erroneousFiles.append(filePath)
74
+
75
+ if len(erroneousFiles):
76
+ raise Exception(f"The following files are not allowed in the directory '{path}'. Only files with the extensions {allowedFileExtensions} are allowed:{[str(filePath) for filePath in erroneousFiles]}")
77
+
78
+ # There were no errorneous files, so return the requested files
79
+ return sorted([filePath for filePath in allowedFiles if filePath.suffix in fileExtensionsToReturn])
37
80
 
38
81
  @staticmethod
39
82
  def get_all_yml_files_from_directory_one_layer_deep(path: str) -> list[pathlib.Path]:
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
  from pydantic import field_validator, ValidationInfo, model_validator, FilePath, model_serializer
3
3
  from typing import TYPE_CHECKING, Optional, Any, Union
4
4
  import re
5
+ import csv
5
6
  if TYPE_CHECKING:
6
7
  from contentctl.input.director import DirectorOutputDto
7
8
  from contentctl.objects.config import validate
@@ -61,15 +62,53 @@ class Lookup(SecurityContentObject):
61
62
  raise ValueError("config required for constructing lookup filename, but it was not")
62
63
  return data
63
64
 
64
- @field_validator('filename')
65
- @classmethod
66
- def lookup_file_valid(cls, v: Union[FilePath,None], info: ValidationInfo):
67
- if not v:
68
- return v
69
- if not (v.name.endswith(".csv") or v.name.endswith(".mlmodel")):
70
- raise ValueError(f"All Lookup files must be CSV files and end in .csv. The following file does not: '{v}'")
71
65
 
72
- return v
66
+ def model_post_init(self, ctx:dict[str,Any]):
67
+ if not self.filename:
68
+ return
69
+ import pathlib
70
+ filenamePath = pathlib.Path(self.filename)
71
+
72
+ if filenamePath.suffix not in [".csv", ".mlmodel"]:
73
+ raise ValueError(f"All Lookup files must be CSV files and end in .csv. The following file does not: '{filenamePath}'")
74
+
75
+
76
+
77
+ if filenamePath.suffix == ".mlmodel":
78
+ # Do not need any additional checks for an mlmodel file
79
+ return
80
+
81
+ # https://docs.python.org/3/library/csv.html#csv.DictReader
82
+ # Column Names (fieldnames) determine by the number of columns in the first row.
83
+ # If a row has MORE fields than fieldnames, they will be dumped in a list under the key 'restkey' - this should throw an Exception
84
+ # If a row has LESS fields than fieldnames, then the field should contain None by default. This should also throw an exception.
85
+ csv_errors:list[str] = []
86
+ with open(filenamePath, "r") as csv_fp:
87
+ RESTKEY = "extra_fields_in_a_row"
88
+ csv_dict = csv.DictReader(csv_fp, restkey=RESTKEY)
89
+ if csv_dict.fieldnames is None:
90
+ raise ValueError(f"Error validating the CSV referenced by the lookup: {filenamePath}:\n\t"
91
+ "Unable to read fieldnames from CSV. Is the CSV empty?\n"
92
+ " Please try opening the file with a CSV Editor to ensure that it is correct.")
93
+ # Remember that row 1 has the headers and we do not iterate over it in the loop below
94
+ # CSVs are typically indexed starting a row 1 for the header.
95
+ for row_index, data_row in enumerate(csv_dict):
96
+ row_index+=2
97
+ if len(data_row.get(RESTKEY,[])) > 0:
98
+ csv_errors.append(f"row [{row_index}] should have [{len(csv_dict.fieldnames)}] columns,"
99
+ f" but instead had [{len(csv_dict.fieldnames) + len(data_row.get(RESTKEY,[]))}].")
100
+
101
+ for column_index, column_name in enumerate(data_row):
102
+ if data_row[column_name] is None:
103
+ csv_errors.append(f"row [{row_index}] should have [{len(csv_dict.fieldnames)}] columns, "
104
+ f"but instead had [{column_index}].")
105
+ if len(csv_errors) > 0:
106
+ err_string = '\n\t'.join(csv_errors)
107
+ raise ValueError(f"Error validating the CSV referenced by the lookup: {filenamePath}:\n\t{err_string}\n"
108
+ f" Please try opening the file with a CSV Editor to ensure that it is correct.")
109
+
110
+ return
111
+
73
112
 
74
113
  @field_validator('match_type')
75
114
  @classmethod
@@ -8,7 +8,6 @@ build = 16367
8
8
 
9
9
  [triggers]
10
10
  reload.analytic_stories = simple
11
- reload.usage_searches = simple
12
11
  reload.use_case_library = simple
13
12
  reload.correlationsearches = simple
14
13
  reload.analyticstories = simple
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: contentctl
3
- Version: 4.1.3
3
+ Version: 4.1.5
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
@@ -3,7 +3,7 @@ contentctl/actions/build.py,sha256=BVc-1E63zeUQ9wWAHTC_fLNvfEK5YT3Z6_QLiE72TQs,4
3
3
  contentctl/actions/convert.py,sha256=0KBWLxvP1hSPXpExePqpOQPRvlQLamvPLyQqeTIWNbk,704
4
4
  contentctl/actions/deploy_acs.py,sha256=mf3uk495H1EU_LNN-TiOsYCo18HMGoEBMb6ojeTr0zw,1418
5
5
  contentctl/actions/detection_testing/DetectionTestingManager.py,sha256=zg8JasDjCpSC-yhseEyUwO8qbDJIUJbhlus9Li9ZAnA,8818
6
- contentctl/actions/detection_testing/GitService.py,sha256=FY_JtMi3qL-uC31Yyf7CTWZ5kLQhAMAvcj-8QXtkfPM,8386
6
+ contentctl/actions/detection_testing/GitService.py,sha256=xNhuvK8oUoTxFlC0XBhlew9V0DO7l2hqaBMffEk5ohM,9000
7
7
  contentctl/actions/detection_testing/generate_detection_coverage_badge.py,sha256=N5mznaeErVak3mOBwsd0RDBFJO3bku0EZvpayCyU-uk,2259
8
8
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py,sha256=VFhSHdw_0N6ol668hDkaj7yFjPsZqBoFNC8FKzWKICc,53141
9
9
  contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py,sha256=HVGWCXy0GQeBqu2cVJn5H-I8GY8rwgkkc53ilO1TfZA,6846
@@ -17,11 +17,11 @@ contentctl/actions/doc_gen.py,sha256=YNc1VYA0ikL1hWDHYjfEOmUkfhy8PEIdvTyC4ZLxQRY
17
17
  contentctl/actions/initialize.py,sha256=2h3_A68mNWcyZjbrKF-OeQXBi5p4Zu3z74K7QxEtII4,1749
18
18
  contentctl/actions/initialize_old.py,sha256=0qXbW_fNDvkcnEeL6Zpte8d-hpTu1REyzHsXOCY-YB8,9333
19
19
  contentctl/actions/inspect.py,sha256=6gVVKmV5CUUYOkNNVTMPKj9bM1uXVthgGCoFKZGDeS8,12628
20
- contentctl/actions/new_content.py,sha256=4gTlxV0fmdsSETPX4T9uQPpIAm--Jf2vc6Vm3w-RkfI,6128
20
+ contentctl/actions/new_content.py,sha256=o5ZYBQ216RN6TnW_wRxVGJybx2SsJ7ht4PAi1dw45Yg,6076
21
21
  contentctl/actions/release_notes.py,sha256=akkFfLhsJuaPUyjsb6dLlKt9cUM-JApAjTFQMbYoXeM,13115
22
22
  contentctl/actions/reporting.py,sha256=MJEmvmoA1WnSFZEU9QM6daL_W94oOX0WXAcX1qAM2As,1583
23
23
  contentctl/actions/test.py,sha256=dx7f750_MrlvysxOmOdIro1bH0iVKF4K54TSwhvU2MU,5146
24
- contentctl/actions/validate.py,sha256=HnmB_qsluYr0BFHQzg0HvXGLHM4M1taBtsWt774esf8,2537
24
+ contentctl/actions/validate.py,sha256=E6bQ0lZkFiFyC4hyRuypKiMybZSE4EXvzd94B0fQUFo,3590
25
25
  contentctl/api.py,sha256=FBOpRhbBCBdjORmwe_8MPQ3PRZ6T0KrrFcfKovVFkug,6343
26
26
  contentctl/contentctl.py,sha256=Vr2cuvaPjpJpYvD9kVoYq7iD6rhLQEpTKmcGoq4emhA,10470
27
27
  contentctl/enrichments/attack_enrichment.py,sha256=EkEloG3hMmPTloPyYiVkhq3iT_BieXaJmprJ5stfyRw,6732
@@ -29,7 +29,7 @@ contentctl/enrichments/cve_enrichment.py,sha256=IzkKSdnQi3JrAUUyLpcGA_Y2g_B7latq
29
29
  contentctl/enrichments/splunk_app_enrichment.py,sha256=zDNHFLZTi2dJ1gdnh0sHkD6F1VtkblqFnhacFcCMBfc,3418
30
30
  contentctl/helper/link_validator.py,sha256=-XorhxfGtjLynEL1X4hcpRMiyemogf2JEnvLwhHq80c,7139
31
31
  contentctl/helper/logger.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
- contentctl/helper/utils.py,sha256=THV4ZuaeEHG6PK5JeZUkTLBmvN4fUV0Ar6opH1zXxKs,16545
32
+ contentctl/helper/utils.py,sha256=8ICRvE7DUiNL9BK4Hw71hCLFbd3R2u86OwKeDOdaBTY,19454
33
33
  contentctl/input/backend_splunk_ba.py,sha256=Y70tJqgaUM0nzfm2SiGMof4HkhY84feqf-xnRx1xPb4,5861
34
34
  contentctl/input/director.py,sha256=BR1RvBD0U_JtHtrM3jM_FpcvaaNlME7nc-gNO4RJLM8,13323
35
35
  contentctl/input/new_content_questions.py,sha256=o4prlBoUhEMxqpZukquI9WKbzfFJfYhEF7a8m2q_BEE,5565
@@ -63,7 +63,7 @@ contentctl/objects/integration_test.py,sha256=W_VksBN_cRo7DTXdr1aLujjS9mgkEp0uvo
63
63
  contentctl/objects/integration_test_result.py,sha256=DrIZRRlILSHGcsK_Rlm3KJLnbKPtIen8uEPFi4ZdJ8s,370
64
64
  contentctl/objects/investigation.py,sha256=JRoZxc_qi1fu_VFTRaxOc3B7zzSzCfEURsNzWPUCrtY,2620
65
65
  contentctl/objects/investigation_tags.py,sha256=nFpMRKBVBsW21YW_vy2G1lXaSARX-kfFyrPoCyE77Q8,1280
66
- contentctl/objects/lookup.py,sha256=P8YbzdDAj_MsTBJTEsym35zhQjiN9Eq0MlfON-qvuTM,4556
66
+ contentctl/objects/lookup.py,sha256=TwNQqeMPeE8sfAjChxS2yDnejI2Xf3ils3_Xdgthr5c,6924
67
67
  contentctl/objects/macro.py,sha256=9nE-bxkFhtaltHOUCr0luU8jCCthmglHjhKs6Q2YzLU,2684
68
68
  contentctl/objects/mitre_attack_enrichment.py,sha256=bWrMG-Xj3knmULR5q2YZk7mloJBdQUzU1moZfEw9lQM,1073
69
69
  contentctl/objects/notable_action.py,sha256=ValkblBaG-60TF19y_vSnNzoNZ3eg48wIfr0qZxyKTA,1605
@@ -135,13 +135,12 @@ contentctl/templates/app_template/README/essoc_summary.txt,sha256=u6wYNYBqmmm7Kn
135
135
  contentctl/templates/app_template/README/essoc_usage_dashboard.txt,sha256=xYUKKVtdgzPyT3mqdTccaBZuwWnC63lbc9zyYpmHN4o,2432
136
136
  contentctl/templates/app_template/README.md,sha256=RT-J9bgRSFsEFgNr9qV6yc2LkfUH_uiMJ2RV4NM9Ymo,366
137
137
  contentctl/templates/app_template/default/analytic_stories.conf,sha256=zWuCOOl8SiP7Kit2s-de4KRu3HySLtBSXcp1QnJx0ec,168
138
- contentctl/templates/app_template/default/app.conf,sha256=eTSq1QI4-BgylZJgnNVg5jQCZFXJVNyEJA33lQAgYoc,685
138
+ contentctl/templates/app_template/default/app.conf,sha256=PrW8TosZ5oVBfpB0SoLxa5vk2ewEAbVKQ6rG8g5WDSQ,654
139
139
  contentctl/templates/app_template/default/commands.conf,sha256=U2ccwUeGXKKKt5jo14QY5swi-p9_TSJtaNquOkeF3Yk,319
140
140
  contentctl/templates/app_template/default/content-version.conf,sha256=TGzX6qLdzRK7x6b0y5AE8ZF59PLU-DrRfS43fVWITqo,34
141
141
  contentctl/templates/app_template/default/data/ui/nav/default.xml,sha256=fKN53HZCtNJbQqq_5pP8e5-5m30DRrJittr6q5s6V_0,236
142
142
  contentctl/templates/app_template/default/data/ui/views/escu_summary.xml,sha256=jQhkIthPgEEptCJ2wUCj2lWGHBvUl6JGsKkDfONloxI,8635
143
143
  contentctl/templates/app_template/default/data/ui/views/feedback.xml,sha256=uM71EMK2uFz8h68nOTNKGnYxob3HhE_caSL6yA-3H-k,696
144
- contentctl/templates/app_template/default/usage_searches.conf,sha256=mFnhAHGhFHIzl8xxA626thnAjyxs5ZQQfur1PP_Xmbg,4257
145
144
  contentctl/templates/app_template/default/use_case_library.conf,sha256=zWuCOOl8SiP7Kit2s-de4KRu3HySLtBSXcp1QnJx0ec,168
146
145
  contentctl/templates/app_template/lookups/mitre_enrichment.csv,sha256=tifPQjFoQHtvpb78hxSP2fKHnHeehNbZDwUjdvc0aEM,66072
147
146
  contentctl/templates/app_template/metadata/default.meta,sha256=tcYHZkDF44ApDoDQ_rp8MCA8cuT3DVd5atHgulR1Tvc,423
@@ -164,8 +163,8 @@ contentctl/templates/detections/web/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRk
164
163
  contentctl/templates/macros/security_content_ctime.yml,sha256=Gg1YNllHVsX_YB716H1SJLWzxXZEfuJlnsgB2fuyoHU,159
165
164
  contentctl/templates/macros/security_content_summariesonly.yml,sha256=9BYUxAl2E4Nwh8K19F3AJS8Ka7ceO6ZDBjFiO3l3LY0,162
166
165
  contentctl/templates/stories/cobalt_strike.yml,sha256=rlaXxMN-5k8LnKBLPafBoksyMtlmsPMHPJOjTiMiZ-M,3063
167
- contentctl-4.1.3.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
168
- contentctl-4.1.3.dist-info/METADATA,sha256=VxqSwKj59aGzVSuESf7w0PefidDnsaFwre-MlIh6nLI,19706
169
- contentctl-4.1.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
170
- contentctl-4.1.3.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
171
- contentctl-4.1.3.dist-info/RECORD,,
166
+ contentctl-4.1.5.dist-info/LICENSE.md,sha256=hQWUayRk-pAiOZbZnuy8djmoZkjKBx8MrCFpW-JiOgo,11344
167
+ contentctl-4.1.5.dist-info/METADATA,sha256=dzhmOQMe0mFoHlZ12p7FsQzHGF-jSKvzbAosP5fTsC8,19706
168
+ contentctl-4.1.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
169
+ contentctl-4.1.5.dist-info/entry_points.txt,sha256=5bjZ2NkbQfSwK47uOnA77yCtjgXhvgxnmCQiynRF_-U,57
170
+ contentctl-4.1.5.dist-info/RECORD,,
@@ -1,73 +0,0 @@
1
- [escu-metrics-usage]
2
- action.email.useNSSubject = 1
3
- alert.digest_mode = True
4
- alert.suppress = 0
5
- alert.track = 0
6
- auto_summarize.dispatch.earliest_time = -1d@h
7
- dispatchAs = user
8
- search = index=_audit sourcetype="audittrail" \
9
- "ESCU - "\
10
- `comment("Find all the search names in the audittrail.")`\
11
- | stats count(search) by search savedsearch_name user\
12
- | eval usage=(if(savedsearch_name=="","Adhoc","Scheduled")) \
13
- `comment("If the savedsearch_name field in the audittrail is empty, the search was run adhoc. Otherwise it was run as a scheduled search")`\
14
- | rex field=search "\"(?<savedsearch_name>.*)\""\
15
- `comment("Extract the name of the search from the search string")`\
16
- | table savedsearch_name count(search) usage user | join savedsearch_name max=0 type=left [search sourcetype="manifests" | spath searches{} | mvexpand searches{} | spath input=searches{} | table category search_name | rename search_name as savedsearch_name | dedup savedsearch_name] | search category=*
17
-
18
- [escu-metrics-search]
19
- action.email.useNSSubject = 1
20
- alert.suppress = 0
21
- alert.track = 0
22
- auto_summarize.dispatch.earliest_time = -1d@h
23
- enableSched = 1
24
- cron_schedule = 0 0 * * *
25
- dispatch.earliest_time = -4h@h
26
- dispatch.latest_time = -1h@h
27
- search = index=_audit action=search | transaction search_id maxspan=3m | search ESCU | stats sum(total_run_time) avg(total_run_time) max(total_run_time) sum(result_count)
28
-
29
- [escu-metrics-search-events]
30
- action.email.useNSSubject = 1
31
- alert.digest_mode = True
32
- alert.suppress = 0
33
- alert.track = 0
34
- auto_summarize.dispatch.earliest_time = -1d@h
35
- cron_schedule = 0 0 * * *
36
- enableSched = 1
37
- dispatch.earliest_time = -4h@h
38
- dispatch.latest_time = -1h@h
39
- search = [search index=_audit sourcetype="audittrail" \"ESCU NOT "index=_audit" | where search !="" | dedup search_id | rex field=search "\"(?<search_name>.*)\"" | rex field=_raw "user=(?<user>[a-zA-Z0-9_\-]+)" | eval usage=if(savedsearch_name!="", "scheduled", "adhoc") | eval savedsearch_name=if(savedsearch_name != "", savedsearch_name, search_name) | table savedsearch_name search_id user _time usage | outputlookup escu_search_id.csv | table search_id] index=_audit total_run_time event_count result_count NOT "index=_audit" | lookup escu_search_id.csv search_id | stats count(savedsearch_name) AS search_count avg(total_run_time) AS search_avg_run_time sum(total_run_time) AS search_total_run_time sum(result_count) AS search_total_results earliest(_time) AS firsts latest(_time) AS lasts by savedsearch_name user usage| eval first_run=strftime(firsts, "%B %d %Y") | eval last_run=strftime(lasts, "%B %d %Y")
40
-
41
- [escu-metrics-search-longest-runtime]
42
- action.email.useNSSubject = 1
43
- alert.digest_mode = True
44
- alert.suppress = 0
45
- alert.track = 0
46
- auto_summarize.dispatch.earliest_time = -1d@h
47
- enableSched = 1
48
- cron_schedule = 0 0 * * *
49
- disabled = 1
50
- dispatch.earliest_time = -4h@h
51
- dispatch.latest_time = -1h@h
52
- search = index=_* ESCU [search index=_* action=search latest=-2h earliest=-1d| transaction search_id maxspan=3m | search ESCU | stats values(total_run_time) AS run by search_id | sort -run | head 1| table search_id] | table search search_id
53
-
54
- [escu-metrics-usage-search]
55
- action.email.useNSSubject = 1
56
- alert.digest_mode = True
57
- alert.suppress = 0
58
- alert.track = 0
59
- auto_summarize.dispatch.earliest_time = -1d@h
60
- cron_schedule = 0 0 * * *
61
- dispatch.earliest_time = -4h@h
62
- dispatch.latest_time = -1h@h
63
- enableSched = 1
64
- dispatchAs = user
65
- search = index=_audit sourcetype="audittrail" \
66
- "ESCU - "\
67
- `comment("Find all the search names in the audittrail. Ignore the last few minutes so we can exclude this search's text from the result.")`\
68
- | stats count(search) by search savedsearch_name user\
69
- | eval usage=(if(savedsearch_name=="","Adhoc","Scheduled")) \
70
- `comment("If the savedsearch_name field in the audittrail is empty, the search was run adhoc. Otherwise it was run as a scheduled search")`\
71
- | rex field=search "\"(?<savedsearch_name>.*)\""\
72
- `comment("Extract the name of the search from the search string")`\
73
- | table savedsearch_name count(search) usage user | join savedsearch_name max=0 type=left [search sourcetype="manifests" | spath searches{} | mvexpand searches{} | spath input=searches{} | table category search_name | rename search_name as savedsearch_name | dedup savedsearch_name] | search category=*