[Ferm] [PATCH RFC] Automatically apply @ipfilter on dual-stack config

Faidon Liambotis paravoid at debian.org
Thu Jun 13 10:35:34 CEST 2013


Add support for ferm to automatically apply @ipfilter on constructs
such as:
    domain (ip ip6) chain INPUT {
        saddr (192.0.2.5 2001:db8::5) proto tcp dport ssh ACCEPT;
    }
and do the obvious.

This explicitly *not* modify the "domain ip" and "domain ip6" cases
(single stack) as ferm should not silently discard such errors but let
ip6?tables complain to the admin.
---
 src/ferm                       |   60 ++++++++++++++++++++++++++++++++--------
 test/misc/address-magic.ferm   |   30 ++++++++++++++++++++
 test/misc/address-magic.result |   22 ++++++++++++++
 3 files changed, 100 insertions(+), 12 deletions(-)
 create mode 100644 test/misc/address-magic.ferm
 create mode 100644 test/misc/address-magic.result

diff --git a/src/ferm b/src/ferm
index 92a4d88..de8df3d 100755
--- a/src/ferm
+++ b/src/ferm
@@ -141,6 +141,7 @@ sub rollback();
 sub execute_fast($);
 sub execute_slow($);
 sub join_value($$);
+sub ipfilter($@);
 
 # add a module definition
 sub add_def_x {
@@ -227,7 +228,8 @@ add_proto_def 'udp', qw();
 
 add_match_def '',
   # --source, --destination
-  qw(source! saddr:=source destination! daddr:=destination),
+  qw(source!&address_magic saddr:=source),
+  qw(destination!&address_magic daddr:=destination),
   # --in-interface
   qw(in-interface! interface:=in-interface if:=in-interface),
   # --out-interface
@@ -433,6 +435,47 @@ sub multiport_params {
     }
 }
 
+sub ipfilter($@) {
+    my $domain = shift;
+    my @ips;
+    # very crude IPv4/IPv6 address detection
+    if ($domain eq 'ip') {
+        @ips = grep { !/:[0-9a-f]*:/ } @_;
+    } elsif ($domain eq 'ip6') {
+        @ips = grep { !m,^[0-9./]+$,s } @_;
+    }
+    return @ips;
+}
+
+sub address_magic {
+    my $rule = shift;
+    my $domain = $rule->{domain};
+    my $value = getvalues(undef, allow_negation => 1);
+
+    my @ips;
+    my $negated = 0;
+    if (ref $value and ref $value eq 'ARRAY') {
+        @ips = @$value;
+    } elsif (ref $value and ref $value eq 'negated') {
+        @ips = @$value;
+        $negated = 1;
+    } elsif (ref $value) {
+        die;
+    } else {
+        @ips = ($value);
+    }
+
+    # only do magic on domain (ip ip6); do not process on a single-stack rule
+    # as to let admins spot their errors instead of silently ignoring them
+    @ips = ipfilter($domain, @ips) if defined $rule->{domain_both};
+
+    if ($negated && scalar @ips) {
+        return bless \@ips, 'negated';
+    } else {
+        return \@ips;
+    }
+}
+
 # initialize stack: command line definitions
 unshift @stack, {};
 
@@ -1239,15 +1282,7 @@ sub getvalues {
             error('Usage: @ipfilter((ip1 ip2 ...))') unless @params == 1;
             my $domain = $stack[0]{auto}{DOMAIN};
             error('No domain specified') unless defined $domain;
-            my @ips = to_array($params[0]);
-
-            # very crude IPv4/IPv6 address detection
-            if ($domain eq 'ip') {
-                @ips = grep { !/:[0-9a-f]*:/ } @ips;
-            } elsif ($domain eq 'ip6') {
-                @ips = grep { !m,^[0-9./]+$,s } @ips;
-            }
-
+            my @ips = ipfilter($domain, to_array($params[0]));
             return \@ips;
         } else {
             error("unknown ferm built-in function");
@@ -1654,7 +1689,7 @@ sub new_level(\%$) {
         $rule->{keywords} = $prev->{keywords};
         $rule->{match} = { %{$prev->{match}} };
         $rule->{options} = [@{$prev->{options}}];
-        foreach my $key (qw(domain domain_family table chain protocol has_rule has_action)) {
+        foreach my $key (qw(domain domain_family domain_both table chain protocol has_rule has_action)) {
             $rule->{$key} = $prev->{$key}
               if exists $prev->{$key};
         }
@@ -2073,6 +2108,7 @@ sub enter($$) {
                         my %inner;
                         new_level(%inner, \%rule);
                         set_domain(%inner, $domain) or next;
+			$inner{domain_both} = 1;
                         $script->{base_level} = 0;
                         $script->{tokens} = [ @$tokens ];
                         enter(0, \%inner);
@@ -2200,7 +2236,7 @@ sub enter($$) {
                               match => {},
                               options => [],
                             );
-                $inner{$_} = $rule{$_} foreach qw(domain domain_family table keywords);
+                $inner{$_} = $rule{$_} foreach qw(domain domain_family domain_both table keywords);
                 $inner{chain} = $inner{auto}{CHAIN} = $subchain;
 
                 if (exists $rule{protocol}) {
diff --git a/test/misc/address-magic.ferm b/test/misc/address-magic.ferm
new file mode 100644
index 0000000..43acb44
--- /dev/null
+++ b/test/misc/address-magic.ferm
@@ -0,0 +1,30 @@
+ at def $VARIABLE_TEST = (192.0.2.6 2001:db8::6);
+ at def $IPFILTER_TEST = (192.0.2.7 2001:db8::7);
+
+domain (ip ip6) table filter chain INPUT {
+	saddr 192.0.2.1 proto tcp dport ssh ACCEPT;
+	saddr (192.0.2.2 192.0.2.3) proto tcp dport ssh ACCEPT;
+	saddr 2001:db8::1 proto tcp dport ssh ACCEPT;
+	saddr (2001:db8::2 2001:db8::3) proto tcp dport ssh ACCEPT;
+
+	saddr (192.0.2.4 2001:db8::4) proto tcp dport ssh ACCEPT;
+
+	saddr ! 192.0.2.5 proto tcp dport ssh ACCEPT;
+	saddr ! 2001:db8::5 proto tcp dport ssh ACCEPT;
+
+	saddr $VARIABLE_TEST proto tcp dport ssh ACCEPT;
+	saddr @ipfilter($IPFILTER_TEST) proto tcp dport ssh ACCEPT;
+
+	saddr localhost proto tcp dport ssh ACCEPT;
+	saddr localhost.localdomain proto tcp dport ssh ACCEPT;
+}
+
+domain ip table filter chain INPUT {
+	saddr 192.0.2.11 proto tcp dport ssh ACCEPT;
+	saddr 2001:db8::11 proto tcp dport ssh ACCEPT;
+}
+
+domain ip6 table filter chain INPUT {
+	saddr 192.0.2.21 proto tcp dport ssh ACCEPT;
+	saddr 2001:db8::21 proto tcp dport ssh ACCEPT;
+}
diff --git a/test/misc/address-magic.result b/test/misc/address-magic.result
new file mode 100644
index 0000000..e15908d
--- /dev/null
+++ b/test/misc/address-magic.result
@@ -0,0 +1,22 @@
+iptables -t filter -A INPUT -s 192.0.2.1 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 192.0.2.2 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 192.0.2.3 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 192.0.2.4 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT ! -s 192.0.2.5 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 192.0.2.6 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 192.0.2.7 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s localhost -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s localhost.localdomain -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 192.0.2.11 -p tcp --dport ssh -j ACCEPT
+iptables -t filter -A INPUT -s 2001:db8::11 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::1 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::2 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::3 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::4 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT ! -s 2001:db8::5 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::6 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::7 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s localhost -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s localhost.localdomain -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 192.0.2.21 -p tcp --dport ssh -j ACCEPT
+ip6tables -t filter -A INPUT -s 2001:db8::21 -p tcp --dport ssh -j ACCEPT
-- 
1.7.2.5



More information about the Ferm mailing list