File Coverage

blib/lib/Ixchel/Actions/suricata_base.pm
Criterion Covered Total %
statement 26 125 20.8
branch 0 30 0.0
condition 0 3 0.0
subroutine 9 13 69.2
pod 2 4 50.0
total 37 175 21.1


line stmt bran cond sub pod time code
1             package Ixchel::Actions::suricata_base;
2              
3 1     1   88522 use 5.006;
  1         3  
4 1     1   3 use strict;
  1         3  
  1         23  
5 1     1   3 use warnings;
  1         1  
  1         59  
6 1     1   588 use File::Slurp;
  1         35127  
  1         69  
7 1     1   399 use YAML::XS qw(Dump);
  1         2667  
  1         53  
8 1     1   407 use Ixchel::functions::file_get;
  1         5  
  1         110  
9 1     1   765 use YAML::yq::Helper;
  1         11458  
  1         47  
10 1     1   29 use File::Temp qw/ tempfile /;
  1         3  
  1         85  
11 1     1   20 use base 'Ixchel::Actions::base';
  1         1  
  1         643  
12              
13             =head1 NAME
14              
15             Ixchel::Actions::suricata_base - Reels in the base Suricata config and uses it for generating the base config for each instance.
16              
17             =head1 VERSION
18              
19             Version 0.4.0
20              
21             =cut
22              
23             our $VERSION = '0.4.0';
24              
25             =head1 CLI SYNOPSIS
26              
27             ixchel -a suricata_base [B<-d> ]
28              
29             ixchel -a suricata_base B<-w> [B<-o> ] [B<--np>] [B<-d> ]
30              
31             =head1 CODE SYNOPSIS
32              
33             use Data::Dumper;
34              
35             my $results=$ixchel->action(action=>'suricata_base', opts=>{np=>1, w=>1, });
36              
37             print Dumper($results);
38              
39             =head1 DESCRIPTION
40              
41             This will fetch the file specied via .suricata.base_config in the config. This is
42             a URL to the config file to use, by default it is
43             https://raw.githubusercontent.com/OISF/suricata/master/suricata.yaml.in .
44              
45             This will be fetched using proxies as defined under .proxy .
46              
47             The following keys are removed.
48              
49             .logging.outputs
50             .outputs
51             .af-packet
52             .pcap
53             .include
54             .rule-files
55             .af-xdp
56             .dpdk
57             .sensor-name
58              
59             =head1 FLAGS
60              
61             =head2 -w
62              
63             Write the generated services to service files.
64              
65             =head2 -i instance
66              
67             A instance to operate on.
68              
69             =head2 -d
70              
71             Use this as the base dir instead of .suricata.config_base from the config.
72              
73             =head1 RESULT HASH REF
74              
75             .errors :: A array of errors encountered.
76             .status_text :: A string description of what was done and teh results.
77             .ok :: Set to zero if any of the above errored.
78              
79             =cut
80              
81       0 0   sub new_extra { }
82              
83             sub action_extra {
84 0     0 0   my $self = $_[0];
85              
86 0           my $config_base;
87 0 0         if ( !defined( $self->{opts}{d} ) ) {
88 0           $config_base = $self->{config}{suricata}{config_base};
89             } else {
90 0 0         if ( !-d $self->{opts}{d} ) {
91             $self->status_add(
92 0           status => '-d, "' . $self->{opts}{d} . '" is not a directory',
93             error => 1
94             );
95              
96 0           return undef;
97             }
98 0           $config_base = $self->{opts}{d};
99             } ## end else [ if ( !defined( $self->{opts}{d} ) ) ]
100              
101 0           my $base_config_url = $self->{config}{suricata}{base_config};
102              
103 0 0         if ( !defined($base_config_url) ) {
104 0           $self->status_add(
105             error => 1,
106             status =>
107             'The config value .config.base_config is undef. It should be the value for URL to fetch it from'
108             );
109 0           return undef;
110             }
111              
112 0           my $base_config_raw;
113 0           eval {
114 0           $self->status_add( status => 'Fetching ' . $base_config_url );
115 0           $base_config_raw = file_get( url => $base_config_url );
116             };
117 0 0         if ($@) {
118 0           $self->status_add( error => 1, status => 'Fetch Error... ' . $@ );
119 0           return undef;
120             }
121              
122             # rebuild the file
123 0           my @base_config_split = split( /\n/, $base_config_raw );
124 0           $base_config_raw = '';
125 0           foreach my $line (@base_config_split) {
126 0           my $value = $self->{config}{suricata}{base_fill_in}{e_logdir};
127 0           $line =~ s/\@e_logdir\@/$value/;
128              
129 0           $value = $self->{config}{suricata}{base_fill_in}{e_magic_file_comment};
130 0           $line =~ s/\@e_magic_file_comment\@/$value/;
131              
132 0           $value = $self->{config}{suricata}{base_fill_in}{e_magic_file};
133 0           $line =~ s/\@e_magic_file\@/$value/;
134              
135 0           $value = $self->{config}{suricata}{base_fill_in}{e_defaultruledir};
136 0           $line =~ s/\@e_defaultruledir\@/$value/;
137              
138 0           $value = $self->{config}{suricata}{config_base} . '/';
139 0           $value =~ s/\/\/+$/\//;
140 0           $line =~ s/\@e_sysconfdir\@/$value/;
141              
142             # remove anything else as we hit the items we actually care about
143 0           $line =~ s/^.*\@.*\@.*$//;
144              
145 0           $base_config_raw = $base_config_raw . $line . "\n";
146             } ## end foreach my $line (@base_config_split)
147 0           my $new_status = 'Base config template processed';
148 0 0         if ( $self->{opts}{pp} ) {
149 0           $new_status = $new_status . "...\n" . $base_config_raw;
150             }
151 0           $self->status_add( status => $new_status );
152              
153             #
154             #
155             # remove unwanted paths
156             #
157             #
158 0           my @to_remove = (
159             '.logging.outputs', '.outputs', '.af-packet', '.pcap',
160             '.include', '.rule-files', '.af-xdp', '.napatech',
161             '.dpdk', '.sensor-name', '.nflog', '.netmap',
162             '.pfring',
163             );
164 0           eval {
165 0           my ( $tnp_fh, $tmp_file ) = tempfile();
166 0           write_file( $tmp_file, $base_config_raw );
167              
168             # use yq here to preserve comments
169 0           my $yq = YAML::yq::Helper->new( file => $tmp_file );
170 0           foreach my $rm_path (@to_remove) {
171 0           $self->status_add( status => 'Removing ' . $rm_path . ' via yq...' );
172 0           $yq->delete( var => $rm_path );
173             }
174              
175 0           $base_config_raw = read_file($tmp_file);
176             };
177 0 0         if ($@) {
178 0           $self->status_add( error => 1, status => 'Errored removing paths... ' . $@ );
179 0           return undef;
180             }
181 0           $new_status = 'Path removal finished';
182 0 0         if ( $self->{opts}{pr} ) {
183 0           $new_status = $new_status . "...\n" . $base_config_raw;
184             }
185 0           $self->status_add( status => $new_status );
186              
187             #
188             #
189             # handle writing the file out
190             #
191             #
192 0 0 0       if ( $self->{config}{suricata}{multi_instance} ) {
    0          
193 0           my @instances;
194              
195 0 0         if ( defined( $self->{opts}{i} ) ) {
196 0           @instances = ( $self->{opts}{i} );
197             } else {
198 0           @instances = keys( %{ $self->{config}{suricata}{instances} } );
  0            
199             }
200 0           foreach my $instance (@instances) {
201 0           eval {
202 0           my ( $tnp_fh, $tmp_file ) = tempfile();
203 0           write_file( $tmp_file, $base_config_raw );
204              
205 0           my @include_paths = (
206             $config_base . '/' . $instance . '-include.yaml',
207             $config_base . '/' . $instance . '-outputs.yaml',
208             );
209              
210 0           my $yq = YAML::yq::Helper->new( file => $tmp_file );
211 0 0         if ( $yq->is_array( var => '.include' ) ) {
212 0           $yq->set_array( var => '.include', vals => \@include_paths );
213             } else {
214 0           $yq->create_array( var => '.include', vals => \@include_paths );
215             }
216              
217 0           $self->status_add( status => 'Adding .include finished' );
218              
219 0           $base_config_raw = read_file($tmp_file);
220 0           $self->status_add( status => "Config... \n" . $base_config_raw );
221 0 0         if ( $self->{opts}{w} ) {
222 0           $self->status_add(
223             status => 'Writing out to ' . $config_base . '/suricata-' . $instance . '.yaml' );
224 0           write_file( $config_base . '/suricata-' . $instance . '.yaml', $base_config_raw );
225             }
226              
227 0           unlink($tmp_file);
228             } ## end eval
229             } ## end foreach my $instance (@instances)
230             } elsif ( defined( $self->{opts}{i} ) && !$self->{config}{suricata}{multi_instance} ) {
231 0           $self->status_add(
232             error => 1,
233             status => '-i may not be used in single instance mode, .suricata.multi_instance=1, ,'
234             );
235             } else {
236 0           eval {
237 0           my ( $tnp_fh, $tmp_file ) = tempfile();
238 0           write_file( $tmp_file, $base_config_raw );
239              
240 0           my @include_paths = ( $config_base . '/include.yaml', $config_base . '/outputs.yaml', );
241              
242 0           my $yq = YAML::yq::Helper->new( file => $tmp_file );
243 0 0         if ( $yq->is_array( var => '.include' ) ) {
244 0           $yq->set_array( var => '.include', vals => \@include_paths );
245             } else {
246 0           $yq->create_array( var => '.include', vals => \@include_paths );
247             }
248              
249 0           $self->status_add( status => 'Adding .include finished' );
250              
251 0           $base_config_raw = read_file($tmp_file);
252 0           $self->status_add( status => "Config... \n" . $base_config_raw );
253 0 0         if ( $self->{opts}{w} ) {
254 0           $self->status_add( status => 'Writing out to ' . $config_base . '/suricata.yaml' );
255 0           write_file( $config_base . '/suricata.yaml', $base_config_raw );
256             }
257              
258 0           unlink($tmp_file);
259             };
260 0 0         if ($@) {
261 0           $self->status_add(
262             error => 1,
263             status => 'Errored adding in include paths or writing file out(if asked)... ' . $@
264             );
265 0           return undef;
266             }
267             } ## end else [ if ( $self->{config}{suricata}{multi_instance...})]
268              
269 0           return undef;
270             } ## end sub action_extra
271              
272             sub short {
273 0     0 1   return 'Reels in the base Suricata config and uses it for generating the base config for each instance.';
274             }
275              
276             sub opts_data {
277 0     0 1   return 'i=s
278             w
279             pp
280             pr
281             pi
282             d=s
283             ';
284             }
285              
286             1;