implemented graphing functionality, embeds diagram of correlation rule elements into Markdown out file

This commit is contained in:
Michael Clemens 2018-03-21 23:03:11 +01:00
parent 54ab6ff680
commit 727ba22f65

View File

@ -21,9 +21,12 @@ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import sys import sys
import os.path import os.path
import re import re
import networkx as nx
from configparser import ConfigParser from configparser import ConfigParser
from lxml import etree from lxml import etree
from urllib.parse import unquote from urllib.parse import unquote
from networkx.drawing.nx_pydot import write_dot
from subprocess import check_call
# Read configuration from ini file # Read configuration from ini file
@ -37,6 +40,7 @@ toc = config.getboolean('config', 'toc')
images = config.getboolean('config', 'images') images = config.getboolean('config', 'images')
imagepath = config.get('config', 'imagepath') imagepath = config.get('config', 'imagepath')
# Generates a line containing linebreaks, indented lists, styles etc. # Generates a line containing linebreaks, indented lists, styles etc.
def line(level,key,value): def line(level,key,value):
@ -59,7 +63,6 @@ def line(level,key,value):
else: output = "" else: output = ""
output = re.sub('\$\$',"!",output) output = re.sub('\$\$',"!",output)
#output = re.sub('\$\$',"PARAMETER:[",output)
return output return output
@ -82,16 +85,77 @@ def sortxml(xmlfile):
temp[:] = [item[-1] for item in data] temp[:] = [item[-1] for item in data]
return root return root
# Generate Markdown Syntax for Images # Generate Markdown Syntax for Images
def addimage(rulename): def addimage(rulename):
out = "" out = ""
imagefile = imagepath + "/" + rulename + ".png" imagefile = imagepath + "/" + rulename + ".png"
imagefile = imagefile.replace(" ", "_") imagefile = imagefile.replace(" ", "_")
if (os.path.isfile(imagefile)): out = "![](" + imagefile + ")\n\n\n"
out = "![](" + imagefile + ")\n\n\n"
return out return out
# Generate dict object with relations between triggers and match blocks
def getRelationDict(cdata):
rel = {}
# Populate Tree with rule objects
for r in cdata.getiterator('rule'):
if not r.get('name') == "Root Rule":
for e in r.iter():
if str(e.tag) == 'action':
if e.get('type') == "TRIGGER":
for trigger in cdata.getiterator('trigger'):
if e.get('trigger') == trigger.get('name'):
rel[r.get('name')]=trigger.get('name')
# Populate Tree with trigger objects
for t in cdata.getiterator('trigger'):
tname = t.get('name')
tparent = t.findtext('trigger')
if tname:
if not tparent:
tparent = "root"
rel[tname]=tparent
return rel
# populate Graph Object with trigger nodes and edges
def addTriggersToGraph(reldict,cdata,G):
# Walk through dict
tco = 99
for key in sorted(reldict):
trigcount = 0
# count triggers per rule
if key.startswith("trigger"):
for trigkey in sorted(reldict):
if reldict[trigkey] == key:
trigcount += 1
# get count value from triggers element
for tc in cdata.getiterator('trigger'):
if tc.get('name') == key:
if tc.get('count'):
tco = int(tc.get('count'))
# compare count value and counted triggers
# if both are the same, all match blocks need to match
# therefore the logical operator is AND
# otherwise its OR
if tco == trigcount:
oper = "AND"
else:
oper = "OR"
# add trigger nodes to graph
G.add_node(key, label=oper, shape='plaintext')
if key != "root" and reldict[key] != "root":
G.add_edge(reldict[key],key,splines='ortho', nodesep=0.2)
return G
# Main Function # Main Function
def main(xmlfile,outfile): def main(xmlfile,outfile):
@ -108,6 +172,7 @@ def main(xmlfile,outfile):
file.write(line(1,rule.findtext('message'),"N/A")) file.write(line(1,rule.findtext('message'),"N/A"))
for rule in root.getiterator('rule'): for rule in root.getiterator('rule'):
G = nx.DiGraph()
# Get CDATA # Get CDATA
text = rule.findtext('text') text = rule.findtext('text')
cdata = etree.fromstring(text) cdata = etree.fromstring(text)
@ -129,7 +194,7 @@ def main(xmlfile,outfile):
file.write(line(1,"Group By:",rs.get('correlationField'))) file.write(line(1,"Group By:",rs.get('correlationField')))
file.write("\n## Correlation Details\n") file.write("\n## Correlation Details\n")
if images: if images:
file.write(addimage(rulename)) file.write(addimage(rule.findtext('id')))
parameters = False parameters = False
# Print rule parameters # Print rule parameters
for param in cdata.getiterator('param'): for param in cdata.getiterator('param'):
@ -139,13 +204,25 @@ def main(xmlfile,outfile):
file.write(line(1,param.get('name'),"N/A")) file.write(line(1,param.get('name'),"N/A"))
file.write(line(2,"Description:",param.get('description'))) file.write(line(2,"Description:",param.get('description')))
file.write(line(2,"Default Value:",param.get('defaultvalue'))) file.write(line(2,"Default Value:",param.get('defaultvalue')))
# get dictionary with all element relations
reldict = getRelationDict(cdata)
# populate Graph object with triggers
G = addTriggersToGraph(reldict,cdata,G)
file.write("\n### Rules\n") file.write("\n### Rules\n")
# Parse CDATA element and print correlation rule match blocks # Parse CDATA element and print correlation rule match blocks
for r in cdata.getiterator('rule'): for r in cdata.getiterator('rule'):
# initialize variables
o = "" o = ""
v = "" v = ""
t = "" t = ""
override = "" override = ""
parent = ""
# Walk through all rules except Root Rule
if not r.get('name') == "Root Rule": if not r.get('name') == "Root Rule":
file.write("\n#### " + r.get('name').title().replace("_", " ") + "\n") file.write("\n#### " + r.get('name').title().replace("_", " ") + "\n")
override = r.get('correlationField') override = r.get('correlationField')
@ -157,8 +234,10 @@ def main(xmlfile,outfile):
if str(e.tag) == 'action': if str(e.tag) == 'action':
if e.get('type') == "TRIGGER": if e.get('type') == "TRIGGER":
file.write(line(1,"Action:","Trigger")) file.write(line(1,"Action:","Trigger"))
# Find parent trigger of current rule
for trigger in cdata.getiterator('trigger'): for trigger in cdata.getiterator('trigger'):
if e.get('trigger') == trigger.get('name'): if e.get('trigger') == trigger.get('name'):
parent = trigger.get('name')
file.write(line(2,"Timeout:",trigger.get('timeout'))) file.write(line(2,"Timeout:",trigger.get('timeout')))
file.write(line(2,"Time Units:",trigger.get('timeUnit'))) file.write(line(2,"Time Units:",trigger.get('timeUnit')))
file.write(line(2,"Threshold:",trigger.get('threshold'))) file.write(line(2,"Threshold:",trigger.get('threshold')))
@ -182,8 +261,19 @@ def main(xmlfile,outfile):
file.write(line(2,"Filter Component","N/A")) file.write(line(2,"Filter Component","N/A"))
file.write(line(3,"Condition:","'" + t + "' " + o + " '" \ file.write(line(3,"Condition:","'" + t + "' " + o + " '" \
+ v + "'")) + v + "'"))
# Set nice label, add rule as graphviz node,
# add edge between trigger and node
label = t + r"\n" + o + r"\n" + v
G.add_node(r.get('name').title().replace("_", " "), \
color='orange',style='filled',fillcolor='orange',shape='box')
G.add_edge(parent,r.get('name').title().replace("_", " "))
v = "" v = ""
o = "" o = ""
# write dot file for Graphviz out to file system
write_dot(G,'file.dot')
# execute 'dot' as os command, generate png file from dot file
check_call(['dot','-Tpng','-Grankdir=LR','file.dot','-o',imagepath + '/' + rule.findtext('id')+'.png'])
file.write("\n\\newpage\n") file.write("\n\\newpage\n")
file.close() file.close()