diff --git a/.gitignore b/.gitignore index bae4a468..2e4fcc96 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ apps* test_results* attack_data* security_content/ +contentctl.yml # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/contentctl/actions/apav_deploy.py b/contentctl/actions/apav_deploy.py deleted file mode 100644 index fcffff97..00000000 --- a/contentctl/actions/apav_deploy.py +++ /dev/null @@ -1,98 +0,0 @@ -import splunklib.client as client -import multiprocessing -import http.server -import time -import sys -import subprocess -import os -class Deploy: - def __init__(self, args): - - - - #First, check to ensure that the legal ack is correct. If not, quit - if args.acs_legal_ack != "Y": - raise(Exception(f"Error - must supply 'acs-legal-ack=Y', not 'acs-legal-ack={args.acs_legal_ack}'")) - - self.acs_legal_ack = args.acs_legal_ack - self.app_package = args.app_package - if not os.path.exists(self.app_package): - raise(Exception(f"Error - app_package file {self.app_package} does not exist")) - self.username = args.username - self.password = args.password - self.server = args.server - - - - self.deploy_to_splunk_cloud() - #self.http_process = self.start_http_server() - - #self.install_app() - - - def deploy_to_splunk_cloud(self): - - commandline = f"acs apps install private --acs-legal-ack={self.acs_legal_ack} "\ - f"--app-package {self.app_package} --server {self.server} --username "\ - f"{self.username} --password {self.password}" - - - try: - res = subprocess.run(args = commandline.split(' '), ) - except Exception as e: - raise(Exception(f"Error deploying to Splunk Cloud Instance: {str(e)}")) - print(res.returncode) - if res.returncode != 0: - raise(Exception("Error deploying to Splunk Cloud Instance. Review output to diagnose error.")) - - ''' - def install_app_local(self) -> bool: - #Connect to the service - time.sleep(1) - #self.http_process.start() - #time.sleep(2) - - - print(f"Connecting to server {self.host}") - try: - service = client.connect(host=self.host, port=self.api_port, username=self.username, password=self.password) - assert isinstance(service, client.Service) - - except Exception as e: - raise(Exception(f"Failure connecting the Splunk Search Head: {str(e)}")) - - - #Install the app - try: - params = {'name': self.server_app_path} - res = service.post('apps/appinstall', **params) - #Check the result? - - print(f"Successfully installed {self.server_app_path}!") - - - - except Exception as e: - raise(Exception(f"Failure installing the app {self.server_app_path}: {str(e)}")) - - - #Query and list all of the installed apps - try: - all_apps = service.apps - except Exception as e: - print(f"Failed listing all apps: {str(e)}") - return False - - print("Installed apps:") - for count, app in enumerate(all_apps): - print("\t{count}. {app.name}") - - - print(f"Installing app {self.path}") - - self.http_process.terminate() - - return True - ''' - - \ No newline at end of file diff --git a/contentctl/actions/api_deploy.py b/contentctl/actions/api_deploy.py deleted file mode 100644 index bb627477..00000000 --- a/contentctl/actions/api_deploy.py +++ /dev/null @@ -1,151 +0,0 @@ -import os -import sys -import json -import requests -from requests.auth import HTTPBasicAuth - -from dataclasses import dataclass -from configparser import RawConfigParser -import splunklib.client as client - -from contentctl.objects.config import Config -import pathlib - -@dataclass(frozen=True) -class API_DeployInputDto: - path: pathlib.Path - config: Config - - -class API_Deploy: - def fix_newlines_in_conf_files(self, conf_path: pathlib.Path) -> RawConfigParser: - parser = RawConfigParser() - with open(conf_path, "r") as conf_data_file: - conf_data = conf_data_file.read() - - # ConfigParser cannot read multipleline strings that simply escape the newline character with \ - # To include a newline, you need to include a space at the beginning of the newline. - # We will simply replace all \NEWLINE with NEWLINESPACE (removing the leading literal \). - # We will discuss whether we intend to make these changes to the underlying conf files - # or just apply the changes here - conf_data = conf_data.replace("\\\n", "\n ") - - parser.read_string(conf_data) - return parser - - def execute(self, input_dto: API_DeployInputDto) -> None: - if len(input_dto.config.deployments.rest_api_deployments) == 0: - raise Exception("No rest_api_deployments defined in 'contentctl.yml'") - app_path = pathlib.Path(input_dto.config.build.path_root)/input_dto.config.build.title - if not app_path.is_dir(): - raise Exception(f"The unpackaged app does not exist at the path {app_path}. Please run 'contentctl build' to generate the app.") - for target in input_dto.config.deployments.rest_api_deployments: - print(f"Deploying '{input_dto.config.build.title}' to target '{target.server}' [{target.description}]") - splunk_args = { - "host": target.server, - "port": target.port, - "username": target.username, - "password": target.password, - "owner": "nobody", - "app": "SplunkEnterpriseSecuritySuite", - } - print("Warning - we are currently deploying all content into the 'search' app. " - "At this time, this means the user does not have to install the app " - "manually, but this will change") - service = client.connect(**splunk_args) - - - macros_parser = self.fix_newlines_in_conf_files( - app_path/"default"/"macros.conf" - ) - import tqdm - - bar_format_macros = ( - f"Deploying macros " - + "{percentage:3.0f}%[{bar:20}]" - + "[{n_fmt}/{total_fmt} | ETA: {remaining}]" - ) - bar_format_detections = ( - f"Deploying saved searches" - + "{percentage:3.0f}%[{bar:20}]" - + "[{n_fmt}/{total_fmt} | ETA: {remaining}]" - ) - for section in tqdm.tqdm( - macros_parser.sections(), bar_format=bar_format_macros - ): - try: - service.post("properties/macros", __stanza=section) - service.post("properties/macros/" + section, **macros_parser[section]) - tqdm.tqdm.write(f"Deployed macro [{section}]") - except Exception as e: - tqdm.tqdm.write(f"Error deploying macro {section}: {str(e)}") - - detection_parser = RawConfigParser() - detection_parser = self.fix_newlines_in_conf_files( - app_path/"default"/"savedsearches.conf", - ) - - - for section in tqdm.tqdm( - detection_parser.sections(), bar_format=bar_format_detections - ): - try: - if section.startswith(input_dto.config.build.prefix): - params = detection_parser[section] - params["name"] = section - response_actions = [] - if ( - input_dto.config.detection_configuration.notable - and input_dto.config.detection_configuration.notable.rule_description - ): - response_actions.append("notable") - if ( - input_dto.config.detection_configuration.rba - and input_dto.config.detection_configuration.rba.enabled - ): - response_actions.append("risk") - params["actions"] = ",".join(response_actions) - params["request.ui_dispatch_app"] = "ES Content Updates" - params["request.ui_dispatch_view"] = "ES Content Updates" - params["alert_type"] = params.pop("counttype") - params["alert_comparator"] = params.pop("relation") - params["alert_threshold"] = params.pop("quantity") - params.pop("enablesched") - - try: - service.saved_searches.delete(section) - #tqdm.tqdm.write(f"Deleted old saved search: {section}") - except Exception as e: - #tqdm.tqdm.write(f"Error deleting savedsearch '{section}' :[{str(e)}]") - pass - - service.post("saved/searches", **params) - tqdm.tqdm.write(f"Deployed savedsearch [{section}]") - - except Exception as e: - tqdm.tqdm.write(f"Error deploying saved search {section}: {str(e)}") - - # story_parser = RawConfigParser() - # story_parser.read(os.path.join(input_dto.path, input_dto.config.build.splunk_app.path, "default", "analyticstories.conf")) - - # for section in story_parser.sections(): - # if section.startswith("analytic_story"): - # params = story_parser[section] - # params = dict(params.items()) - # params["spec_version"] = 1 - # params["version"] = 1 - # name = section[17:] - # #service.post('services/analyticstories/configs/analytic_story', name=name, content=json.dumps(params)) - - # url = "https://3.72.220.157:8089/services/analyticstories/configs/analytic_story" - # data = dict() - # data["name"] = name - # data["content"] = params - # print(json.dumps(data)) - # response = requests.post( - # url, - # auth=HTTPBasicAuth('admin', 'fgWFshd0mm7eErMj9qX'), - # data=json.dumps(data), - # verify=False - # ) - # print(response.text) diff --git a/contentctl/actions/acs_deploy.py b/contentctl/actions/deploy_acs.py similarity index 100% rename from contentctl/actions/acs_deploy.py rename to contentctl/actions/deploy_acs.py diff --git a/contentctl/actions/inspect.py b/contentctl/actions/inspect.py index 27210ed2..9c46abae 100644 --- a/contentctl/actions/inspect.py +++ b/contentctl/actions/inspect.py @@ -61,7 +61,7 @@ def inspectAppAPI(self, config: inspect)->str: if not package_path.is_file(): raise Exception(f"Cannot run Appinspect API on App '{config.app.title}' - " f"no package exists as expected path '{package_path}'.\nAre you " - "trying to 'contentctl acs_deploy' the package BEFORE running 'contentctl build'?") + "trying to 'contentctl deploy_acs' the package BEFORE running 'contentctl build'?") files = { "app_package": open(package_path,"rb"), diff --git a/contentctl/contentctl.py b/contentctl/contentctl.py index f374ef8d..eb7d0daa 100644 --- a/contentctl/contentctl.py +++ b/contentctl/contentctl.py @@ -5,7 +5,7 @@ import tyro from contentctl.actions.initialize import Initialize -from contentctl.objects.config import init, validate, build, new, deploy_acs, deploy_rest, test, test_servers, inspect, report, test_common, release_notes +from contentctl.objects.config import init, validate, build, new, deploy_acs, test, test_servers, inspect, report, test_common, release_notes from contentctl.actions.validate import Validate from contentctl.actions.new_content import NewContent from contentctl.actions.detection_testing.GitService import GitService @@ -96,11 +96,7 @@ def new_func(config:new): def deploy_acs_func(config:deploy_acs): #This is a bit challenging to get to work with the default values. - raise Exception("deploy acs not yet implemented") - -def deploy_rest_func(config:deploy_rest): - raise Exception("deploy rest not yet implemented") - + raise Exception("deploy acs not yet implemented") def test_common_func(config:test_common): director_output_dto = build_func(config) @@ -176,8 +172,7 @@ def main(): "test":test.model_validate(config_obj), "test_servers":test_servers.model_construct(**t.__dict__), "release_notes": release_notes.model_construct(**config_obj), - "deploy_acs": deploy_acs.model_construct(**t.__dict__), - #"deploy_rest":deploy_rest() + "deploy_acs": deploy_acs.model_construct(**t.__dict__) } ) @@ -210,8 +205,6 @@ def main(): elif type(config) == deploy_acs: updated_config = deploy_acs.model_validate(config) deploy_acs_func(updated_config) - elif type(config) == deploy_rest: - deploy_rest_func(config) elif type(config) == test or type(config) == test_servers: if type(config) == test: #construct the container Infrastructure objects diff --git a/contentctl/objects/config.py b/contentctl/objects/config.py index 96e652af..20f77ed2 100644 --- a/contentctl/objects/config.py +++ b/contentctl/objects/config.py @@ -273,14 +273,6 @@ class Infrastructure(BaseModel): instance_name: str = Field(...) -class deploy_rest(build): - model_config = ConfigDict(use_enum_values=True,validate_default=True, arbitrary_types_allowed=True) - - target:Infrastructure = Infrastructure(instance_name="splunk_target_host", instance_address="localhost") - #This will overwrite existing content without promprting for confirmation - overwrite_existing_content:bool = Field(default=True, description="Overwrite existing macros and savedsearches in your enviornment") - - class Container(Infrastructure): model_config = ConfigDict(use_enum_values=True,validate_default=True, arbitrary_types_allowed=True) instance_address:str = Field(default="localhost", description="Address of your splunk server.")