Repository: SigmaHQ/sigma

Issue:
[Feature Request] Multiple output with ElastAlert conversion
# [Feature Request] Multiple output with ElastAlert conversion

### Request Type
Feature Request

### Problem Description
When your want use sigmac for converting rules in elastalert rules.. Can we have 1 file per rule ??

For the moment .. if i do (the option recursive is "On") :

/root/sigma/tools/sigmac --target elastalert-dsl --config /root/sigma/tools/config/winlogbeat.yml -r -I --output /root/rules/rule.yml /root/sigma/rules/

I have 1 file with all rules inside, but ElastAlert don't load all rules... Only the first.

Can you integrate the possibility to having the output multiple files with the option recursive??
Example:

/root/sigma/rules/win_GPO_scheduledtasks.yml
/root/sigma/rules/win_account_backdoor_dcsync_rights.yml
...

Thanks for you works :)


Discussion:
While not part of sigmac itself, maybe something like this would work:

# Convert rules to single output file
/path/to/sigma/tools/sigmac --config winlogbeat --target elastalert --recurse /path/to/sigma/rules/windows/ --ignore-backend-errors --output sigma_single_file_output

# Convert output file into multiple files using csplit
csplit --prefix sigma_ --suffix-format "%04d.yaml" sigma_single_file_output "/^alert:/" "{*}"
# Delete the original output file
rm sigma_single_file_output
# Delete the first file as it will always be empty - This does it dynamically by finding empty files in the current directory
find . -size  0 -print -delete

Does that help?

Resolving patch:
diff --git a/tools/sigma/sigmac.py b/tools/sigma/sigmac.py
--- a/tools/sigma/sigmac.py
+++ b/tools/sigma/sigmac.py
@@ -102,7 +102,8 @@ def set_argparser():
     argparser.add_argument("--target", "-t", choices=backends.getBackendDict().keys(), help="Output target format")
     argparser.add_argument("--lists", "-l", action="store_true", help="List available output target formats and configurations")
     argparser.add_argument("--config", "-c", action="append", help="Configurations with field name and index mapping for target environment. Multiple configurations are merged into one. Last config is authoritative in case of conflicts.")
-    argparser.add_argument("--output", "-o", default=None, help="Output file or filename prefix if multiple files are generated")
+    argparser.add_argument("--output", "-o", default=None, help="Output file or filename prefix (if end with a '_','/' or '\\')")
+    argparser.add_argument("--output-extention", "-e", default=None, help="Extention of Output file for filename prefix use")
     argparser.add_argument("--print0", action="store_true", help="Delimit results by NUL-character")
     argparser.add_argument("--backend-option", "-O", action="append", help="Options and switches that are passed to the backend")
     argparser.add_argument("--backend-config", "-C", help="Configuration file (YAML format) containing options to pass to the backend")
@@ -135,7 +136,6 @@ def list_modifiers(modifiers):
 def main():
     argparser = set_argparser()
     cmdargs = argparser.parse_args()
-
     scm = SigmaConfigurationManager()
 
     logger = logging.getLogger(__name__)
@@ -214,14 +214,27 @@ def main():
 
     backend_options = BackendOptions(cmdargs.backend_option, cmdargs.backend_config)
     backend = backend_class(sigmaconfigs, backend_options)
-
+    
+    filename_ext = cmdargs.output_extention
     filename = cmdargs.output
+    fileprefix = None
     if filename:
-        try:
-            out = open(filename, "w", encoding='utf-8')
-        except (IOError, OSError) as e:
-            print("Failed to open output file '%s': %s" % (filename, str(e)), file=sys.stderr)
-            exit(ERR_OUTPUT)
+        if filename_ext:
+            if filename_ext[0] == '.':
+                pass
+            else:
+                filename_ext = '.' + filename_ext
+        else:
+            filename_ext = '.rule'
+    
+        if filename[-1:] in ['_','/','\\']:
+            fileprefix = filename
+        else:
+            try:
+                out = open(filename, "w", encoding='utf-8')
+            except (IOError, OSError) as e:
+                print("Failed to open output file '%s': %s" % (filename, str(e)), file=sys.stderr)
+                exit(ERR_OUTPUT)
     else:
         out = sys.stdout
 
@@ -235,10 +248,34 @@ def main():
                 f = sigmafile.open(encoding='utf-8')
             parser = SigmaCollectionParser(f, sigmaconfigs, rulefilter, sigmafile)
             results = parser.generate(backend)
-
+            
+            nb_result = len(list(parser.generate(backend)))
+            if nb_result > 1 :
+                inc_filenane = 0
+            else:
+                inc_filenane = None
+            
             newline_separator = '\0' if cmdargs.print0 else '\n'
             for result in results:
+                if not fileprefix == None and not inc_filenane == None: #yml action
+                    try:
+                        filename = fileprefix + str(sigmafile.name)
+                        filename = filename.replace('.yml','_' + str(inc_filenane) + filename_ext)
+                        inc_filenane += 1
+                        out = open(filename, "w", encoding='utf-8')
+                    except (IOError, OSError) as e:
+                        print("Failed to open output file '%s': %s" % (filename, str(e)), file=sys.stderr)
+                        exit(ERR_OUTPUT)
+                elif  not fileprefix == None and inc_filenane == None: # a simple yml
+                    try:
+                        filename = fileprefix + str(sigmafile.name)
+                        filename = filename.replace('.yml',filename_ext) 
+                        out = open(filename, "w", encoding='utf-8')
+                    except (IOError, OSError) as e:
+                        print("Failed to open output file '%s': %s" % (filename, str(e)), file=sys.stderr)
+                        exit(ERR_OUTPUT)
                 print(result, file=out, end=newline_separator)
+        
         except OSError as e:
             print("Failed to open Sigma file %s: %s" % (sigmafile, str(e)), file=sys.stderr)
             error = ERR_OPEN_SIGMA_RULE


Test patch: