File Coverage

lib/Rex/Resource/firewall/Provider/ufw.pm
Criterion Covered Total %
statement 17 150 11.3
branch 0 94 0.0
condition 0 37 0.0
subroutine 6 18 33.3
pod 0 6 0.0
total 23 305 7.5


line stmt bran cond sub pod time code
1             #
2             # (c) Andrew Beverley
3             # (c) Jan Gehring
4             #
5              
6             package Rex::Resource::firewall::Provider::ufw;
7              
8 1     1   16 use v5.12.5;
  1         3  
9 1     1   5 use warnings;
  1         4  
  1         42  
10              
11             our $VERSION = '1.14.2.3'; # TRIAL VERSION
12              
13 1     1   7 use Data::Dumper;
  1         3  
  1         104  
14 1     1   8 use Rex::Commands::Run;
  1         2  
  1         8  
15 1     1   7 use Rex::Helper::Run;
  1         3  
  1         79  
16              
17 1     1   8 use base qw(Rex::Resource::firewall::Provider::base);
  1         4  
  1         1872  
18              
19             my %__action_map = (
20             accept => "allow",
21             allow => "allow",
22             deny => "deny", ## -j DROP
23             drop => "deny", ## -j DROP
24             reject => "reject", ## -j REJECT
25             limit => "limit",
26             );
27              
28             sub new {
29 0     0 0   my $that = shift;
30 0   0       my $proto = ref($that) || $that;
31 0           my $self = $proto->SUPER::new(@_);
32              
33 0           bless( $self, $proto );
34              
35 0           return $self;
36             }
37              
38             sub present {
39 0     0 0   my ( $self, $rule_config ) = @_;
40              
41 0           my @ufw_params = $self->_generate_rule_array($rule_config);
42              
43 0           return $self->_ufw_rule( grep { defined } @ufw_params );
  0            
44             }
45              
46             sub absent {
47 0     0 0   my ( $self, $rule_config ) = @_;
48              
49 0           $rule_config->{delete} = 1;
50 0           my @ufw_params = $self->_generate_rule_array($rule_config);
51              
52 0           return $self->_ufw_rule( grep { defined } @ufw_params );
  0            
53             }
54              
55             sub enable {
56 0     0 0   my ( $self, $rule_config ) = @_;
57 0           return $self->_ufw_disable_enable("enable");
58             }
59              
60             sub disable {
61 0     0 0   my ( $self, $rule_config ) = @_;
62 0           return $self->_ufw_disable_enable("disable");
63             }
64              
65             sub logging {
66 0     0 0   my ( $self, $logging ) = @_;
67 0           return $self->_ufw_logging($logging);
68             }
69              
70             sub _ufw_rule {
71              
72 0     0     my ( $self, $action, @params ) = @_;
73 0           my %torun = (
74             action => $action, # allow, deny, limit etc
75             commands => [],
76             );
77              
78 0           my $has_app; # Has app parameters
79             my $has_port; # Has port parameters
80              
81 0           while ( my $param = shift @params ) {
82 0 0 0       if ( $param eq 'proto' ) {
    0          
    0          
    0          
    0          
    0          
    0          
83 0           my $proto = shift @params;
84 0 0 0       die "Invalid protocol $proto"
85             unless ( $proto eq 'tcp' || $proto eq 'udp' );
86 0           $torun{proto} = "proto $proto";
87             }
88             elsif ( $param eq 'from' || $param eq 'to' ) {
89 0           my $address = shift @params;
90 0           push @{ $torun{commands} }, ( $param => $address );
  0            
91              
92             # See if next rule is a port
93 0 0 0       if ( $params[0] && $params[0] eq 'port' ) {
    0 0        
94 0           shift @params;
95 0           my $port = shift @params;
96 0           push @{ $torun{commands} }, ( port => $port );
  0            
97 0           $has_port = 1;
98             }
99             elsif ( $params[0] && $params[0] eq 'app' ) {
100 0           shift @params;
101 0           my $app = shift @params;
102 0           push @{ $torun{commands} }, ( app => $app );
  0            
103 0           $has_app = 1;
104             }
105             }
106             elsif ( $param eq 'app' ) {
107              
108             # App can appear on its own, or in combination with from/to above
109 0           my $app = shift @params;
110 0           $torun{app} = $app;
111 0           $has_app = 1;
112             }
113             elsif ( $param eq 'direction' ) {
114 0           my $direction = shift @params;
115 0 0 0       die "Invalid direction $direction"
116             unless ( $direction eq 'in' || $direction eq 'out' );
117 0           $torun{direction} = $direction;
118              
119             # See if next rule is an interface
120 0 0 0       if ( $params[0] && $params[0] eq 'on' ) {
121 0           shift @params;
122 0           my $interface = shift @params;
123 0           $torun{on} = "on $interface";
124             }
125             }
126             elsif ( $param eq 'log' ) {
127 0           my $log = shift @params;
128 0 0         if ( $log eq 'new' ) {
    0          
129 0           $torun{log} = 'log';
130             }
131             elsif ( $log eq 'all' ) {
132 0           $torun{log} = 'log-all';
133             }
134             else {
135 0           die "Invalid logging option $log";
136             }
137             }
138             elsif ( $param eq 'delete' ) {
139 0 0         $torun{delete} = 'delete' if shift @params;
140             }
141             elsif ( $param =~ m/^\d+(\/(tcp|udp))?$/ ) {
142 0 0         if ( scalar @{ $torun{commands} } == 0 ) {
  0            
143 0           push @{ $torun{commands} }, $param;
  0            
144             }
145             }
146             else {
147 0           die qq(Unexpected parameter "$param" supplied to ufw rule $action);
148             }
149             }
150              
151 0 0 0       die "Do not specify port parameter with app parameter"
152             if $has_app && $has_port;
153              
154             die "Do not specify protocol parameter with app parameter"
155 0 0 0       if $has_app && $torun{proto};
156              
157 0           my $cmd;
158 0           for my $param (qw/delete action direction on log app proto/) {
159 0 0         $cmd .= " $torun{$param}" if $torun{$param};
160             }
161 0           $cmd .= " @{$torun{commands}}";
  0            
162              
163 0           my $return = $self->_ufw_exec($cmd);
164              
165 0 0         if ( $return =~ /(inserted|updated|deleted|added)/ ) {
166 0           return 1;
167             }
168              
169 0           return 0;
170             }
171              
172             sub _ufw_disable_enable {
173 0     0     my $self = shift;
174 0           my $action = shift;
175 0           my $return = $self->_ufw_exec('status');
176              
177 0 0         my $needed = $action eq 'enable' ? 'inactive' : 'active';
178 0 0         if ( $return =~ /Status: $needed/ ) {
179 0           my $ret = $self->_ufw_exec("--force $action");
180 0 0         my $success =
181             $action eq 'enable'
182             ? 'Firewall is active and enabled'
183             : 'Firewall stopped and disabled';
184 0 0         if ( $ret =~ /$success/ ) {
185 0           return 1;
186             }
187             else {
188 0           Rex::Logger::info( "Unexpected ufw response: $ret", "warn" );
189             }
190             }
191              
192 0           return 0;
193             }
194              
195             sub _ufw_logging {
196 0     0     my $self = shift;
197 0           my $param = shift;
198              
199 0 0         $param =~ /(on|off|low|medium|high|full)/
200             or die "Invalid logging parameter: $param";
201              
202 0           my $current = $self->_ufw_exec('status verbose');
203              
204 0           my $need_update;
205 0 0         if ( $param eq 'on' ) {
    0          
206 0 0         $need_update = 1 unless $current =~ /^Logging: on/m;
207             }
208             elsif ( $param eq 'off' ) {
209 0 0         $need_update = 1 unless $current =~ /^Logging: off$/m;
210             }
211             else {
212 0 0         $need_update = 1 unless $current =~ /^Logging: on \($param\)$/m;
213             }
214              
215 0 0         if ($need_update) {
216 0           my $ret = $self->_ufw_exec("logging $param");
217 0 0         my $success =
218             $param eq 'off'
219             ? 'Logging disabled'
220             : 'Logging enabled';
221 0 0         if ( $ret eq $success ) {
222 0           return 1;
223             }
224             else {
225 0           Rex::Logger::info( "Unexpected ufw response: $ret", "warn" );
226             }
227             }
228             }
229              
230             sub _ufw_exec {
231 0     0     my $self = shift;
232 0           my $cmd = shift;
233              
234 0           $cmd = "ufw $cmd";
235              
236 0 0         if ( can_run("ufw") ) {
237 0     0     my ( $output, $err ) = i_run $cmd, sub { @_ }, fail_ok => 1;
  0            
238              
239 0 0         if ( $? != 0 ) {
240 0           Rex::Logger::info( "Error running ufw command: $cmd, received $err",
241             "warn" );
242 0           die("Error running ufw rule: $cmd");
243             }
244 0           Rex::Logger::debug("Output from ufw: $output");
245 0           return $output;
246             }
247             else {
248 0           Rex::Logger::info("UFW not found.");
249 0           die("UFW not found.");
250             }
251             }
252              
253             sub _generate_rule_array {
254 0     0     my ( $self, $rule_config ) = @_;
255              
256             my $action = $__action_map{ $rule_config->{action} }
257 0 0         or die qq(Unknown action "$rule_config->{action}" for UFW provider);
258 0   0       $rule_config->{dport} ||= $rule_config->{port};
259 0   0       $rule_config->{dapp} ||= $rule_config->{app};
260 0   0       $rule_config->{source} ||= "any";
261 0   0       $rule_config->{destination} ||= "any";
262              
263 0           my @ufw_params = ();
264 0           push @ufw_params, $action;
265              
266             push( @ufw_params, "proto", $rule_config->{proto} )
267 0 0         if ( defined $rule_config->{proto} );
268              
269             push( @ufw_params, "from", $rule_config->{source} )
270 0 0         if ( defined $rule_config->{source} );
271              
272             push( @ufw_params, "port", $rule_config->{sport} )
273 0 0         if ( defined $rule_config->{sport} );
274              
275             push( @ufw_params, "app", qq("$rule_config->{sapp}") )
276 0 0         if ( defined $rule_config->{sapp} );
277              
278             push( @ufw_params, "to", $rule_config->{destination} )
279 0 0         if ( defined $rule_config->{destination} );
280              
281             push( @ufw_params, "port", $rule_config->{dport} )
282 0 0         if ( defined $rule_config->{dport} );
283              
284             push( @ufw_params, "app", qq("$rule_config->{dapp}") )
285 0 0         if ( defined $rule_config->{dapp} );
286              
287             push( @ufw_params, "direction", "in" )
288 0 0         if ( defined $rule_config->{iniface} );
289              
290             push( @ufw_params, "on", $rule_config->{iniface} )
291 0 0         if ( defined $rule_config->{iniface} );
292              
293             push( @ufw_params, "log", $rule_config->{log} )
294 0 0         if ( defined $rule_config->{log} );
295              
296             push( @ufw_params, "delete", $rule_config->{delete} )
297 0 0         if ( defined $rule_config->{delete} );
298              
299 0           return @ufw_params;
300             }
301              
302             1;