2019-11-11 22:35:16 +00:00
#!/usr/bin/env python3
# Assign UUIDs to Sigma rules and verify UUID assignment for a Sigma rule repository
from argparse import ArgumentParser
from pathlib import Path
from uuid import uuid4 , UUID
import yaml
from sigma . output import SigmaYAMLDumper
def print_verbose ( * arg , * * kwarg ) :
2020-03-31 09:35:21 +00:00
print ( * arg , * * kwarg )
2019-11-11 22:35:16 +00:00
# Define order-preserving representer from dicts/maps
def yaml_preserve_order ( self , dict_data ) :
return self . represent_mapping ( " tag:yaml.org,2002:map " , dict_data . items ( ) )
2020-03-31 09:30:14 +00:00
def main ( ) :
argparser = ArgumentParser ( description = " Assign and verfify UUIDs of Sigma rules " )
argparser . add_argument ( " --verify " , " -V " , action = " store_true " , help = " Verify existence and uniqueness of UUID assignments. Exits with error code if verification fails. " )
argparser . add_argument ( " --verbose " , " -v " , action = " store_true " , help = " Be verbose. " )
argparser . add_argument ( " --recursive " , " -r " , action = " store_true " , help = " Recurse into directories. " )
argparser . add_argument ( " --error " , " -e " , action = " store_true " , help = " Exit with error code 10 on verification failures. " )
argparser . add_argument ( " inputs " , nargs = " + " , help = " Sigma rule files or repository directories " )
args = argparser . parse_args ( )
2019-11-11 22:35:16 +00:00
2020-03-31 09:35:21 +00:00
if args . verbose :
print_verbose ( )
2020-03-31 09:30:14 +00:00
if args . recursive :
paths = [ p for pathname in args . inputs for p in Path ( pathname ) . glob ( " **/* " ) if p . is_file ( ) ]
2019-11-11 22:35:16 +00:00
else :
2020-03-31 09:30:14 +00:00
paths = [ Path ( pathname ) for pathname in args . inputs ]
yaml . add_representer ( dict , yaml_preserve_order )
uuids = set ( )
passed = True
for path in paths :
print_verbose ( " Rule {} " . format ( str ( path ) ) )
with path . open ( " r " ) as f :
rules = list ( yaml . safe_load_all ( f ) )
if args . verify :
i = 1
for rule in rules :
if " title " in rule : # Rule with a title should also have a UUID
try :
UUID ( rule [ " id " ] )
except ValueError : # id is not a valid UUID
print ( " Rule {} in file {} has a malformed UUID ' {} ' . " . format ( i , str ( path ) , rule [ " id " ] ) )
passed = False
except KeyError : # rule has no id
print ( " Rule {} in file {} has no UUID. " . format ( i , str ( path ) ) )
passed = False
i + = 1
else :
newrules = list ( )
changed = False
i = 1
for rule in rules :
if " title " in rule and " id " not in rule : # only assign id to rules that have a title and no id
newrule = dict ( )
changed = True
for k , v in rule . items ( ) :
newrule [ k ] = v
if k == " title " : # insert id after title
uuid = uuid4 ( )
newrule [ " id " ] = str ( uuid )
print ( " Assigned UUID ' {} ' to rule {} in file {} . " . format ( uuid , i , str ( path ) ) )
newrules . append ( newrule )
else :
newrules . append ( rule )
i + = 1
if changed :
with path . open ( " w " ) as f :
yaml . dump_all ( newrules , f , Dumper = SigmaYAMLDumper , indent = 4 , width = 160 , default_flow_style = False )
2019-11-11 22:35:16 +00:00
2020-03-31 09:30:14 +00:00
if not passed :
print ( " The Sigma rules listed above don ' t have an ID. The ID must be: " )
print ( " * Contained in the ' id ' attribute " )
print ( " * a valid UUIDv4 (randomly generated) " )
print ( " * Unique in this repository " )
2020-03-31 14:29:58 +00:00
print ( " Please generate one with the sigma_uuid tool or here: https://www.uuidgenerator.net/version4 " )
2020-03-31 09:30:14 +00:00
if args . error :
exit ( 10 )
2019-11-11 22:35:16 +00:00
2020-03-31 09:30:14 +00:00
if __name__ == " __main__ " :
main ( )