Had a few in-between other activities and came up with this. RE: bonus on ideas - "..things that make you go Hmmmmm....." lol
Including if useful to anyone else - this appears to solve the riddle..... Not "pretty" but addresses the need with a few options that can be applied to satisfy some conditions. Could probably be augmented to enable specification of "interface" to further simplify.
Spoiler
#!/usr/local/bin/perl
#
use strict;
use Getopt::Long;
$| = 1;
#
GetOptions('debug'=>\$PROC::DEBUG,'include=s'=>\$PROC::INCLUDE,'severity=s'=>\$PROC::SEVERITY,'targetfile=s'=>\$PROC::TGTFILE,'mergefile=s'=>\$PROC::MRGFILE,);
if (defined($PROC::DEBUG)) { $PROC::DEBUG=1; } else { $PROC::DEBUG=0; }
%PROC::INCS=();
if (defined($PROC::INCLUDE)) {
foreach(split(/,/,$PROC::INCLUDE)) { $PROC::INCS{$_}=0; }
}
#
@PROC::DIRS=('/usr/local/share/suricata/rules','/usr/local/etc/snort/rules',);
%PROC::HASH=();
#
foreach(@PROC::DIRS) {
my $RDIR=$_;
opendir(DIR, "$RDIR"); rewinddir(DIR);
while(my $FILE=readdir(DIR)) {
if ($FILE=~/\.rules$/) {
my $CHECK=$FILE; $CHECK=~s/\.rules$//;
if ((keys(%PROC::INCS)>0) && (exists($PROC::INCS{$CHECK}))) { &procFile("$RDIR/$FILE"); } elsif
(keys(%PROC::INCS)==0) { &procFile("$RDIR/$FILE"); }
}
}
closedir(DIR);
}
#
if (defined($PROC::MRGFILE)) {
open(INF, "<$PROC::MRGFILE");
while(my $LINE=<INF>) {
chomp($LINE);
if (($LINE!~/^#/) && ($LINE!~/^[[:space:]]{0,}$/) && ($LINE=~/^suppress[[:space:]]{1,}/)) {
my $GID=$LINE; $GID=~s/^.*gen_id[[:space:]]{1,}//; $GID=~s/,.*//;
my $SID=$LINE; $SID=~s/^.*.sig_id[[:space:]]{1,}//; if ($SID=~/,/) { $SID=~s/,.*//; }
if (exists($PROC::HASH{$GID}{$SID})) { delete($PROC::HASH{$GID}{$SID}); }
}
}
close(INF);
}
my $FH;
if (defined($PROC::TGTFILE)) { open $FH, ">", "$PROC::TGTFILE" || die("ERROR: $PROC::TGTFILE $!\n"); select($FH); } elsif
(defined($PROC::MRGFILE)) { open $FH, ">>", "$PROC::MRGFILE" || die("ERROR: $PROC::MRGFILE $!\n"); select($FH); print $FH ("\n"); }
foreach my $ID (keys %PROC::HASH) {
foreach my $SID (sort {$a<=>$b} keys %{$PROC::HASH{$ID}}) {
my $MSG=$PROC::HASH{$ID}{$SID}{msg};
my $FILE=$PROC::HASH{$ID}{$SID}{file};
print ("# ($FILE) $MSG\nsuppress gen_id $ID, sig_id $SID\n\n");
}
}
if (defined($PROC::TGTFILE) || defined($PROC::MRGFILE)) { close $FH; }
#
if (defined($PROC::MRGFILE)) {
my $F = do { local $/ = undef; open my $FH, "<", "$PROC::MRGFILE"; <$FH>; };
$F=~s/\n//g; $F=~s/#/\n\n#/g; $F=~s/suppress[[:space:]]{1,}/\nsuppress /g; $F=~s/^\n{1,}//;
open(OUF, ">$PROC::MRGFILE"); print OUF ("$F\n"); close(OUF);
}
#
sub procFile {
my ($FILE)=(shift);
if ($PROC::DEBUG==1) { print ("\tFILE : $FILE\n"); }
open(INF, "<$FILE");
while(my $LINE=<INF>) {
chomp($LINE);
if ($LINE=~/^alert ip \[/) {
my $SID=$LINE; $SID=~s/^.*.sid://; $SID=~s/;.*//;
my $MSG=$LINE; $MSG=~s/^.*.msg://; $MSG=~s/;.*//; $MSG=~s/"//g;
my $SEV=$LINE; $SEV=~s/^.*.signature_severity //; $SEV=~s/,.*//;
my $F=$FILE; $F=~s/\.rules$//; $F=~s/^.*.\///;
if ($PROC::DEBUG==1) { print ("\t\t1 : $SID : $F : $SEV\n"); }
if (defined($PROC::SEVERITY) && ($SEV eq $PROC::SEVERITY)) {
$PROC::HASH{1}{$SID}{msg}=$MSG;
$PROC::HASH{1}{$SID}{file}=$F;
} elsif (!defined($PROC::SEVERITY)) {
$PROC::HASH{1}{$SID}{msg}=$MSG;
$PROC::HASH{1}{$SID}{file}=$F;
}
}
}
close(INF);
return;
}
#
__END__
##
## Documentation
##
=head1 NAME
generate-suppress.pl
=head1 SYNOPSIS
generate-suppress.pl --debug --include=<include> --severity=<severity> --targetfile=<targetfile> --mergefile=<mergefile>
=head1 DESCRIPTION
Generates suppression data from source rules for "alert ip" style entries
=head1 FUNCTION
Insert or merge alert ip suppression data for SNORT/Suricata
=head1 OPTIONS
=over
=item <debug>
Enables debugging output.
=item <include>
Specifies which rule(s) to include in the resultant data. Comma separated - B<NO> spaces.
=item <severity>
Filter resultant rules to only those that match severity.
(As of creation, appears to be either "Major" or "Minor")
=item <targetfile>
If the target file exists, it B<WILL> be overwritten with the results.
=item <mergefile>
If the mergefile already exists, it will be read and only [missing] deltas will be added.
=item NOTE
If neither targetfile nor mergefile are specified, the results are printed to STDOUT.
=back
=head1 COMMON USAGE
perl generate-suppress.pl --mergefile=/usr/local/etc/suricata/suricata_<ID>_<interface>/threshold.config
=cut