From b8d4eff22a8df07668376deac6e98dedb400ddbc Mon Sep 17 00:00:00 2001 From: Matthew Virga <89219147+mvirga-sumo@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:50:57 -0400 Subject: [PATCH] Add Build Signal Output Raw Break Lines (email) This action allows for formatting of signal data from the GetInsightV2 action specifically for emails. --- ...ignal Output Raw Break Lines (email) .yaml | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 CloudSOAR/Integrations/Automation-Tools/actions/Build Signal Output Raw Break Lines (email) .yaml diff --git a/CloudSOAR/Integrations/Automation-Tools/actions/Build Signal Output Raw Break Lines (email) .yaml b/CloudSOAR/Integrations/Automation-Tools/actions/Build Signal Output Raw Break Lines (email) .yaml new file mode 100644 index 0000000..12f4c63 --- /dev/null +++ b/CloudSOAR/Integrations/Automation-Tools/actions/Build Signal Output Raw Break Lines (email) .yaml @@ -0,0 +1,224 @@ +integration: 'Automation Tools' +name: 'Build Signal Output Raw Break Lines (email)' +type: Custom +script: + code: | + import json + import sys + import argparse + import traceback + from datetime import datetime + import pytz + import os + import re + + def determine_and_format_timestamp(timestamp): + formats = [ + "%Y-%m-%dT%H:%M:%S%z", + "%d/%m/%Y %H:%M:%S", + "%Y %b %d %H:%M:%S.%f %Z", + "%b %d %H:%M:%S %Z %Y", + "%d/%b/%Y:%H:%M:%S %Z", + "%b %d, %Y %I:%M:%S %p", + "%b %d %Y %H:%M:%S", + "%b %d %H:%M:%S %Y", + "%b %d %H:%M:%S %Z", + "%b %d %H:%M:%S", + "%Y-%m-%dT%H:%M:%S%z", + "%Y-%m-%dT%H:%M:%SZ", + "%Y-%m-%d %H:%M:%S %Z", + "%Y-%m-%d %H:%M:%S%z", + "%Y-%m-%d %H:%M:%S,%f", + "%Y/%m/%d*H:%M:%S", + "%Y %b %d %H:%M:%S.%f*%Z", + "%Y %b %d %H:%M:%S.%f", + "%Y-%m-%d %H:%M:%S,%f%Z", + "%Y-%m-%d %H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%dT%H:%M:%SZ", + "%Y-%m-%dT%H:%M:%S.%f", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d*H:%M:%S:%f", + "%Y-%m-%d*H:%M:%S", + "%y-%m-%d %H:%M:%S,%f %Z", + "%y-%m-%d %H:%M:%S,%f", + "%y-%m-%d %H:%M:%S %Z", + "%y-%m-%d %H:%M:%S", + "%y/%m/%d %H:%M:%S", + "%y%m%d %H:%M:%S", + "%Y%m%d %H:%M:%S.%f", + "%m/%d/%y*H:%M:%S", + "%m/%d/%Y*H:%M:%S", + "%m/%d/%Y*H:%M:%S*%f", + "%m/%d/%y %H:%M:%S %Z", + "%m/%d/%Y %H:%M:%S %Z", + "%H:%M:%S", + "%H:%M:%S.%f", + "%H:%M:%S,%f", + "%d/%b %H:%M:%S,%f", + "%d/%b/%Y:%H:%M:%S,%f", + "%d/%b/%Y %H:%M:%S", + "%d-%b-%Y %H:%M:%S", + "%d %b %Y %H:%M:%S", + "%d %b %Y %H:%M:%S.%f", + "%m%d_%H:%M:%S", + "%m%d_%H:%M:%S.%f", + "%m/%d/%Y %I:%M:%S %p" + ] + + for date_format in formats: + try: + # Try parsing the timestamp with the current format + dt_object = datetime.strptime(timestamp, date_format) + + # Return the formatted timestamp and format type if successful + return dt_object.strftime(date_format), date_format + except ValueError: + pass + + # If none of the formats match, raise an exception or return a default value + raise ValueError("Invalid timestamp format") + + def sanitize_json(json_string): + # Replace single quotes with double quotes + json_string = json_string.replace("'", "\'") + return json_string + + + def format_signals(signal,output_timezone,exclude_fields,include_fields): + aggregate_list = [] + excludes_list = exclude_fields + includes_list=include_fields + + # Initialize aggregate list + aggregate_list = [] + + # Extract relevant information + name = signal['name'] + signal_time = signal['timestamp'] + entity_name = signal['entity']['name'] + #summary = signal['description'] + rule_id = signal['ruleId'] + stage = signal['stage'] + additional_fields = "" + + for record in signal['allRecords'][0]: + if excludes_list or not includes_list: + if signal['allRecords'][0][record] != {} and signal['allRecords'][0][record] != [] and record != "fields" and record not in excludes_list: + newField= signal['allRecords'][0][record] + if type(newField) == str: + newField= newField.replace('\\"', "'") + newField= newField.replace('"', "'") + newField= newField.replace('\\', '\\\\') + additional_fields += f"
{str(record)}: {newField}" + elif includes_list: + if signal['allRecords'][0][record] != {} and signal['allRecords'][0][record] != [] and record != "fields" and record in includes_list: + newField= signal['allRecords'][0][record] + if type(newField) == str: + newField= newField.replace('\\"', "'") + newField= newField.replace('"', "'") + newField= newField.replace('\\', '\\\\') + additional_fields += f"
{str(record)}: {newField}" + #formatTime + formatted_timestamp, timestamp_format = determine_and_format_timestamp(signal_time) + if "%z" not in timestamp_format and "%Z" not in timestamp_format: + formatted_timestamp = formatted_timestamp+"+0000" + timestamp_format = timestamp_format+"%z" + signal_time = datetime.strptime(formatted_timestamp, timestamp_format) + signal_time = signal_time.astimezone(pytz.timezone(output_timezone)) + + # Format output as a map and add to the aggregate list + formatted_map = {"signaldata": f"
Signal time: {signal_time}
Name: {name}
Rule ID: {rule_id}
Stage: {stage}
Entity Name: {entity_name}{additional_fields}
"} + #formatted_map = {"signaldata": f"\\nSignal time: {signal_time}\\nSignal Name: {name}\\nRule: {rule_name}\\nStage: {stage}\\nEntity Name: {entity_name}{additional_fields}\\n"} + + return formatted_map + + class EnvDefault(argparse.Action): + def __init__(self, required=False, default=None, **kwargs): + envvar = kwargs.get("dest") + default = os.environ.get(envvar, default) if envvar in os.environ else default + required = False if required and default else required + super(EnvDefault, self).__init__(default=default, required=required, **kwargs) + + def __call__(self, parser, namespace, values, option_string=None): + setattr(namespace, self.dest, values) + try: + json_dict = [] + parser = argparse.ArgumentParser() + parser.add_argument('--input_str', help='JSON', required=True, action=EnvDefault) + parser.add_argument('--output_timezone', help='valid TZ identifiers', required=False, action=EnvDefault) + parser.add_argument('--exclude_fields', help='Fields to Exlude from output', required=False, action=EnvDefault) + parser.add_argument('--include_fields', help='Fields to Exlude from output', required=False, action=EnvDefault) + args, unknown = parser.parse_known_args() + input_str = args.input_str + try: + with open(input_str) as json_file: + json_data = json.load(json_file) + if isinstance(json_data, dict): + enrichment_json = json_data.get("message", json_data) + else: + enrichment_json = json_data + except Exception as __: + enrichment_json = json.loads(str(input_str).strip(), strict=False) + output_timezone = args.output_timezone + #exclude_fields = args.exclude_fields + if args.exclude_fields is not None: + exclude_fields = list(args.exclude_fields.split(",")) + else: + exclude_fields = [] + if args.include_fields is not None: + include_fields = list(args.include_fields.split(",")) + else: + include_fields = [] + if not output_timezone: + output_timezone="UTC" + # Example usage with JSON input string + # sanitized_input = sanitize_json(input_str) + + # recorddata = json.loads(sanitized_input) + for signal in enrichment_json['signals']: + format_signal = format_signals(signal,output_timezone,exclude_fields,include_fields) + json_dict.append(format_signal) + + combined_signaldata = "".join(signals['signaldata'] for signals in json_dict) + # Convert the dictionary to JSON format using json.dumps() + wrapped_json = { + "signals": [combined_signaldata] + } + + print(json.dumps(wrapped_json)) + exit(0) + except ValueError as e: + err = str(e) + sys.stderr.write(str(e)) + exit(-1) + +fields: + - id: input_str + label: 'String Input' + incident_artifacts: true + type: text + required: true + hint: 'Accepts valid string input and returns escaped string in JSON format.' + - id: output_timezone + label: 'Display Time of Timezone Output' + type: text + required: false + hint: 'Accepts valid TZ identifiers: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones' + - id: exclude_fields + label: 'Fields to Exclude from output' + type: tag + required: false + - id: include_fields + label: 'Fields to include from output' + type: tag + required: false + hint: 'Exclude fields take precedence over include fields.' + +output: + - path: 'input_string' + type: text + - path: 'signals' + type: text + \ No newline at end of file