File Coverage

blib/lib/IPTables/Mangle.pm
Criterion Covered Total %
statement 40 42 95.2
branch 8 12 66.6
condition 1 2 50.0
subroutine 5 5 100.0
pod 1 1 100.0
total 55 62 88.7


line stmt bran cond sub pod time code
1             package IPTables::Mangle;
2 1     1   823 use strict;
  1         2  
  1         38  
3 1     1   5 use warnings;
  1         2  
  1         737  
4              
5             our $VERSION = '0.04';
6              
7             =head1 NAME
8              
9             IPTables::Mangle - Manage iptables rules with Perl / YAML
10              
11             =head1 SYNOPSIS
12              
13             Given a config file, produces rules for iptables-restore.
14              
15             Example YAML file, for ease of viewing:
16              
17             filter:
18             forward: { default: drop }
19             foo:
20             rules:
21             - src: 9.9.9.9
22             - src: 10.10.10.10
23             action: drop
24             input:
25             # by default, do not allow any connections unless authorized
26             # in the rules below
27             default: drop
28              
29             # by default, if no "action" is given to a rule below, accept it
30             default_rule_action: accept
31              
32             rules:
33             # Accept all traffic on loopback interface
34             - in-interface: lo
35              
36             # Don't disconnect existing connections during a rule change.
37             - { match: state, state: 'ESTABLISHED,RELATED' }
38              
39             # Allow for pings (no more than 10 a second)
40             - { protocol: icmp, icmp-type: 8, match: limit, limit: 10/sec }
41              
42             # Allow these IPs, no matter what
43             - src: 123.123.123.123
44              
45             # example of blocking an IP
46             - { action: drop, src: 8.8.8.8 }
47              
48             # example of allowing ip to connect to port 25 (smtp) (one-line)
49             - { protocol: tcp, dport: 25, src: 4.2.2.2 }
50              
51             # jump to rules defined in "foo" above
52             - action: foo
53              
54             # if there are no more rules, reject the connection with icmp, don't just let it hang
55             - action: reject
56             action_options:
57             reject-with: icmp-admin-prohibited
58              
59             =head1 DESCRIPTION
60              
61             This module allows for the management of iptables rules with Perl / YAML.
62              
63             =head1 TABLES
64              
65             The top hashref is the table for iptables, this can be either mangle, nat, or filter.
66              
67             =head1 CHAINS
68              
69             The hashref under the top hashref is the chain name. For system chains the default chainrule can
70             be set by setting a default hashref in the chain.
71              
72             $VAR1->{filter}{input} would be the input chain for the filter table.
73              
74             =head1 CHAIN RULES
75              
76             Chainrules live in a 'rules' arrayref under the chain, $VAR1->{filter}{input}{rules}, for example.
77              
78             Every rule in the chain is a hashref which builds a rule. By default, the jump in the rules, referenced
79             as 'action' in a rule, is set to accept. The default action can be modified by changing
80             'default_rule_action' in the chain. Every key in the rule's hashref represents a parameter prefixed by two dashes, '--',
81             in an iptables rule. Two things to note here are that 'action' in a rule really maps to 'jump' in iptables, and
82             a special action_options key exists, which references a hashref, which appends options after the iptables jump. This is
83             useful for things like setting '--reject-with' after a jump to reject.
84              
85              
86             Examples of a chain rule:
87              
88              
89             # by default, allow this ip
90              
91             $VAR1->{filter}{input}{rules}[0] = { src => '10.10.10.10' } ;
92              
93              
94             # allow this ip on port 25 tcp, using accept default
95              
96             $VAR1->{filter}{input}{rules}[1] = { protocol: 'tcp', dport: 25, src => '10.10.10.10' } ;
97              
98              
99             # make it explicit
100              
101             $VAR1->{filter}{input}{rules}[2] = { protocol: 'tcp', dport: 25, src => '10.10.10.10', action => 'accept' } ;
102              
103              
104             # blacklist an ip
105              
106             $VAR1->{filter}{input}{rules}[3] = { src => '10.10.10.10', action => 'drop' } ;
107              
108             # reject with icmp message
109              
110             $VAR1->{filter}{input}{rules}[-1] = {
111             action => 'reject',
112             action_options => {
113             reject-with: 'icmp-admin-prohibited',
114             },
115             };
116              
117             =cut
118              
119             =head1 METHODS
120              
121             =head2 process_config
122              
123             Given a hashref, produces rules usable by iptables-restore.
124              
125             Returns one string.
126              
127             =cut
128              
129             sub process_config
130             {
131 1     1 1 25958 my $config = shift;
132              
133 1         3 my $config_out = '';
134              
135 1         8 $config_out .= _process_table({
136             table => 'mangle',
137             chains => [ qw(prerouting input forward output postrouting) ],
138             config => $config,
139             });
140              
141 1         7 $config_out .= _process_table({
142             table => 'nat',
143             chains => [ qw(prerouting postrouting output) ],
144             config => $config,
145             });
146              
147 1         9 $config_out .= _process_table({
148             table => 'filter',
149             chains => [ qw(input forward output) ],
150             config => $config,
151             });
152              
153 1         5 return $config_out;
154             }
155              
156             sub _process_table
157             {
158 3     3   4 my $args = shift;
159              
160 3         5 my $table = $args->{table};
161 3         4 my $config = $args->{config};
162              
163 3         4 my $table_out = '';
164 3         5 $table_out .= "*$table\n";
165              
166             # configure built-in chains
167 3         35 $table_out .= ":" . uc($_) . ' ' .
168             (exists $config->{$table}{$_} ?
169             uc($config->{$table}{$_}{default}) : 'ACCEPT') . "\n"
170 3 100       4 for @{$args->{chains}};
171              
172             # configure custom chains
173 3 50       4 for my $chain (keys %{$config->{$table} || {} })
  3         14  
174             {
175             # skip built-in chains
176 2 50       3 next if grep { $chain eq $_ } @{$args->{chains}};
  6         13  
  2         3  
177              
178 0         0 $table_out .= ":" . uc($chain) . " -\n";
179             }
180              
181 3 50       6 for my $chain (keys %{$config->{$table} || {} })
  3         9  
182             {
183 2 100       29 $table_out .= &_process_rule({
184             target => (
185             uc($_->{action} || '')
186             || uc($config->{$table}{$chain}{default_rule_action} || '')
187             || 'ACCEPT'),
188             chain => uc($chain),
189             rule => $_
190 2   50     3 }) . "\n" for @{$config->{filter}{$chain}{rules} || []};
191             }
192              
193 3         5 $table_out .= "COMMIT\n";
194              
195 3         7 return $table_out;
196             }
197              
198             sub _process_rule
199             {
200 8     8   11 my $args = shift;
201 8         14 my $output = "-A $args->{chain} ";
202              
203             # remove action from rule
204 8         11 delete $args->{rule}{action};
205              
206             # grab action options
207 8         11 my $action_opts = delete $args->{rule}{action_options};
208 8         23 my $action_opts_str = '';
209              
210 8 50       15 if ($action_opts)
211             {
212             $action_opts_str .= "--$_ $action_opts->{$_} "
213 0         0 for keys %$action_opts;
214             }
215              
216 8         40 $output .= "--$_ $args->{rule}{$_} "
217 8         5 for keys %{$args->{rule}};
218              
219 8         19 $output .= "-j $args->{target} $action_opts_str";
220              
221 8         94 return $output;
222             }
223              
224             1;
225              
226             =head1 AUTHORS
227              
228             Bizowie
229              
230             =head1 COPYRIGHT AND LICENSE
231              
232             Copyright (C) 2013 Bizowie
233              
234             This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself.
235              
236             =cut