Skip to content

D assistant take 2 #355

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 39 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c65e501
progress towards d assistant. not working yet
pyth0n1c Jan 23, 2025
c6503b8
Merge branch 'main' into d_assistant_take_2
pyth0n1c Jan 23, 2025
4283eb2
progress towards deprecation assistant,
pyth0n1c Jan 24, 2025
5258a6a
Merge branch 'main' into d_assistant_take_2
pyth0n1c Jan 29, 2025
c942cca
more progress on
pyth0n1c Jan 29, 2025
c5067ee
Huge cleanup around object construction
pyth0n1c Feb 3, 2025
4dc2a01
Merge branch 'main' into d_assistant_take_2
pyth0n1c Feb 14, 2025
ebbc02f
cleanup director. add containing folder
pyth0n1c Feb 17, 2025
5f5f43c
Merge branch 'main' into d_assistant_take_2
pyth0n1c Feb 22, 2025
28af8e8
clean up the director a bit
pyth0n1c Feb 24, 2025
1448eb0
remove duplcate line at
pyth0n1c Feb 25, 2025
9acf456
A number of improvements and restructuring of
pyth0n1c Feb 26, 2025
a378482
fix deprecation file name
pyth0n1c Feb 26, 2025
bfdc941
Merge branch 'main' into d_assistant_take_2
pyth0n1c Feb 26, 2025
c37084c
More deprecation progress
pyth0n1c Mar 4, 2025
fd86570
more deprecation progress
pyth0n1c Mar 6, 2025
1565de2
Merge branch 'main' into d_assistant_take_2
pyth0n1c Mar 10, 2025
697910e
We are now generating a deprecation CSV in
pyth0n1c Mar 10, 2025
68e1d32
More progress on writing deprecation CSV to
pyth0n1c Mar 10, 2025
d488456
progress on exceptions for content
pyth0n1c Mar 10, 2025
12152cd
better deprecation failure error strings
pyth0n1c Mar 10, 2025
a70c3e7
roll back status removed and remove
pyth0n1c Mar 17, 2025
f64095c
Don't try to populate Investigations
pyth0n1c Mar 18, 2025
e6bce99
Many changes to meet new structure/naming
pyth0n1c Mar 18, 2025
78835ad
Merge branch 'main' into d_assistant_take_2
pyth0n1c Apr 2, 2025
9e247f1
Add status to all
pyth0n1c Apr 3, 2025
02eb478
more deprecation assistant
pyth0n1c Apr 7, 2025
7313e60
Big set of changes enforcing ContentStatus correctly
pyth0n1c Apr 8, 2025
bb323c2
director updates to require the presence
pyth0n1c Apr 8, 2025
e613648
Still needs a bit of work, specifically
pyth0n1c Apr 9, 2025
590456e
fish some issues where
pyth0n1c Apr 9, 2025
106286b
update typing on one field
pyth0n1c Apr 9, 2025
5daf801
cleanup around naming. construction
pyth0n1c Apr 11, 2025
b8866f3
Allow multiple deprecation/removal
pyth0n1c Apr 16, 2025
2ee2736
Merge branch 'main' into d_assistant_take_2
pyth0n1c Apr 16, 2025
e7ea913
Make presence of deprecation
pyth0n1c Apr 18, 2025
5b7565c
Fix Ruff type warning
pyth0n1c Apr 18, 2025
24b7c2e
bump version in prep for release
pyth0n1c Apr 18, 2025
11cd116
Make sure that contentctl init creates all the
pyth0n1c Apr 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 5 additions & 43 deletions contentctl/actions/build.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import datetime
import json
import pathlib
import shutil

from dataclasses import dataclass

from contentctl.input.director import DirectorOutputDto
from contentctl.objects.config import build
from contentctl.output.api_json_output import ApiJsonOutput
from contentctl.output.conf_output import ConfOutput
from contentctl.output.conf_writer import ConfWriter
from contentctl.output.api_json_output import ApiJsonOutput
from contentctl.output.data_source_writer import DataSourceWriter
from contentctl.objects.lookup import CSVLookup, Lookup_Type
import pathlib
import json
import datetime
import uuid

from contentctl.objects.config import build


@dataclass(frozen=True)
Expand All @@ -28,39 +23,6 @@ def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
updated_conf_files: set[pathlib.Path] = set()
conf_output = ConfOutput(input_dto.config)

# Construct a path to a YML that does not actually exist.
# We mock this "fake" path since the YML does not exist.
# This ensures the checking for the existence of the CSV is correct
data_sources_fake_yml_path = (
input_dto.config.getPackageDirectoryPath()
/ "lookups"
/ "data_sources.yml"
)

# Construct a special lookup whose CSV is created at runtime and
# written directly into the lookups folder. We will delete this after a build,
# assuming that it is successful.
data_sources_lookup_csv_path = (
input_dto.config.getPackageDirectoryPath()
/ "lookups"
/ "data_sources.csv"
)

DataSourceWriter.writeDataSourceCsv(
input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path
)
input_dto.director_output_dto.addContentToDictMappings(
CSVLookup.model_construct(
name="data_sources",
id=uuid.UUID("b45c1403-6e09-47b0-824f-cf6e44f15ac8"),
version=1,
author=input_dto.config.app.author_name,
date=datetime.date.today(),
description="A lookup file that will contain the data source objects for detections.",
lookup_type=Lookup_Type.csv,
file_path=data_sources_fake_yml_path,
)
)
updated_conf_files.update(conf_output.writeHeaders())
updated_conf_files.update(
conf_output.writeLookups(input_dto.director_output_dto.lookups)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@

from pydantic import BaseModel

from contentctl.objects.config import test_common

from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructure import (
DetectionTestingManagerOutputDto,
)
from contentctl.helper.utils import Utils
from contentctl.objects.enums import DetectionStatus
from contentctl.objects.base_test_result import TestResultStatus
from contentctl.objects.config import test_common
from contentctl.objects.enums import ContentStatus


class DetectionTestingView(BaseModel, abc.ABC):
Expand Down Expand Up @@ -117,11 +116,11 @@ def getSummaryObject(
total_skipped += 1

# Aggregate production status metrics
if detection.status == DetectionStatus.production:
if detection.status == ContentStatus.production:
total_production += 1
elif detection.status == DetectionStatus.experimental:
elif detection.status == ContentStatus.experimental:
total_experimental += 1
elif detection.status == DetectionStatus.deprecated:
elif detection.status == ContentStatus.deprecated:
total_deprecated += 1

# Check if the detection is manual_test
Expand Down
44 changes: 35 additions & 9 deletions contentctl/actions/initialize.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import shutil
import os
import pathlib
import shutil

from contentctl.objects.baseline import Baseline
from contentctl.objects.config import test
from contentctl.objects.dashboard import Dashboard
from contentctl.objects.data_source import DataSource
from contentctl.objects.deployment import Deployment
from contentctl.objects.detection import Detection
from contentctl.objects.investigation import Investigation
from contentctl.objects.lookup import Lookup
from contentctl.objects.macro import Macro
from contentctl.objects.playbook import Playbook
from contentctl.objects.removed_security_content_object import (
RemovedSecurityContentObject,
)
from contentctl.objects.story import Story
from contentctl.output.yml_writer import YmlWriter


Expand All @@ -13,21 +27,33 @@ def execute(self, config: test) -> None:

YmlWriter.writeYmlFile(str(config.path / "contentctl.yml"), config.model_dump())

# Create the following empty directories:
# Create the following empty directories. Each type of content,
# even if you don't have any of that type of content, need its own directory to exist.
for contentType in [
Detection,
Playbook,
Story,
DataSource,
Investigation,
Macro,
Lookup,
Dashboard,
Baseline,
Deployment,
RemovedSecurityContentObject,
]:
contentType.containing_folder().mkdir(exist_ok=False, parents=True)

# Some other directories that do not map directly to a piece of content also must exist

for emptyDir in [
"lookups",
"baselines",
"data_sources",
"docs",
"reporting",
"investigations",
"detections/application",
"detections/cloud",
"detections/endpoint",
"detections/network",
"detections/web",
"macros",
"stories",
]:
# Throw an error if this directory already exists
(config.path / emptyDir).mkdir(exist_ok=False, parents=True)
Expand Down Expand Up @@ -60,7 +86,7 @@ def execute(self, config: test) -> None:
source_directory = pathlib.Path(os.path.dirname(__file__)) / templateDir
target_directory = config.path / targetDir
# Throw an exception if the target exists
shutil.copytree(source_directory, target_directory, dirs_exist_ok=False)
shutil.copytree(source_directory, target_directory, dirs_exist_ok=True)

# Create a README.md file. Note that this is the README.md for the repository, not the
# one which will actually be packaged into the app. That is located in the app_template folder.
Expand Down
23 changes: 8 additions & 15 deletions contentctl/actions/validate.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import pathlib

from contentctl.input.director import Director, DirectorOutputDto
from contentctl.objects.config import validate
from contentctl.enrichments.attack_enrichment import AttackEnrichment
from contentctl.enrichments.cve_enrichment import CveEnrichment
from contentctl.objects.atomic import AtomicEnrichment
from contentctl.objects.lookup import FileBackedLookup
from contentctl.helper.splunk_app import SplunkApp
from contentctl.helper.utils import Utils
from contentctl.input.director import Director, DirectorOutputDto
from contentctl.objects.atomic import AtomicEnrichment
from contentctl.objects.config import validate
from contentctl.objects.data_source import DataSource
from contentctl.helper.splunk_app import SplunkApp
from contentctl.objects.lookup import FileBackedLookup, RuntimeCSV


class Validate:
Expand All @@ -17,16 +17,6 @@ def execute(self, input_dto: validate) -> DirectorOutputDto:
AtomicEnrichment.getAtomicEnrichment(input_dto),
AttackEnrichment.getAttackEnrichment(input_dto),
CveEnrichment.getCveEnrichment(input_dto),
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
)

director = Director(director_output_dto)
Expand Down Expand Up @@ -68,7 +58,10 @@ def ensure_no_orphaned_files_in_lookups(
usedLookupFiles: list[pathlib.Path] = [
lookup.filename
for lookup in director_output_dto.lookups
# Of course Runtime CSVs do not have underlying CSV files, so make
# sure that we do not check for that existence.
if isinstance(lookup, FileBackedLookup)
and not isinstance(lookup, RuntimeCSV)
] + [
lookup.file_path
for lookup in director_output_dto.lookups
Expand Down
35 changes: 14 additions & 21 deletions contentctl/helper/utils.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,34 @@
import os
import git
import shutil
import requests
import logging
import pathlib
import random
import shutil
import string
from math import ceil
from timeit import default_timer
import pathlib
import logging
from typing import TYPE_CHECKING, Tuple, Union

from typing import Union, Tuple
import git
import requests
import tqdm
from math import ceil

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from contentctl.objects.security_content_object import SecurityContentObject
from contentctl.objects.security_content_object import SecurityContentObject


TOTAL_BYTES = 0
ALWAYS_PULL = True


class Utils:
@staticmethod
def get_all_yml_files_from_directory(path: str) -> list[pathlib.Path]:
listOfFiles: list[pathlib.Path] = []
base_path = pathlib.Path(path)
if not base_path.exists():
return listOfFiles
for dirpath, dirnames, filenames in os.walk(path):
for file in filenames:
if file.endswith(".yml"):
listOfFiles.append(pathlib.Path(os.path.join(dirpath, file)))
def get_all_yml_files_from_directory(path: pathlib.Path) -> list[pathlib.Path]:
if not path.exists():
raise FileNotFoundError(
f"Trying to find files in the directory '{path.absolute()}', but it does not exist.\n"
"It is not mandatory to have content/YMLs in this directory, but it must exist. Please create it."
)

return sorted(listOfFiles)
return sorted(pathlib.Path(yml_path) for yml_path in path.glob("**/*.yml"))

@staticmethod
def get_security_content_files_from_directory(
Expand Down
Loading
Loading