Puppet: System Administration Automated

Support

IPTables Module

The iptables module illustrated will help administer iptables rules. There are essentially three parts: the module manifest, which declares an iptables class and a defined-type for adding fragments, the rebuild-iptables script that rebuilds the iptables rules when a fragment is added or removed, and an area for fragments.

modules/iptables/manifests/init.pp

# Handles iptables concerns.  See also ipt_fragment definition
define ipt_fragment($ensure) {
    case $ensure {
        absent: {
            file { "/etc/iptables.d/$name":
                ensure => absent,
            }
        }
        present: {
            file {
               "/etc/iptables.d/$name":
                    source => "puppet://puppet/iptables/fragments/$name",
                    notify => Exec[rebuild_iptables],
            }
        }
    }
}

class iptables {
    package { "iptables":
        ensure => present
    }

    exec { "rebuild_iptables":
        command => "/usr/sbin/rebuild-iptables",
        refreshonly => true,
        require => File["/usr/sbin/rebuild-iptables"],
    }

    file {
        "/etc/iptables.d":
            ensure => directory,
            purge => true,
            notify => Exec["rebuild_iptables"];
        "/usr/sbin/rebuild-iptables":
            source => "puppet://puppet/iptables/rebuild-iptables";
    }
}

modules/iptables/files/rebuild-iptables

#!/usr/bin/perl -w
our $ID = q$Id: rebuild-iptables 344 2006-10-04 02:48:30Z digant $;
#
# rebuild-iptables -- Construct an iptables rules file from fragments.
#
# Written by Russ Allbery <rra@stanford.edu>
# Adapted by Digant C Kasundra <digant@stanford.edu>
# Copyright 2005, 2006 Board of Trustees, Leland Stanford Jr. University
#
# Constructs an iptables rules file from the prefix, standard, and suffix
# files in the iptables configuration area, adding any additional modules
# specified in the command line, and prints the resulting iptables rules to
# standard output (suitable for saving into /var/lib/iptables or some other
# appropriate location on the system).

##############################################################################
# Modules and declarations
##############################################################################

require 5.006;
use strict;

use Getopt::Long qw(GetOptions);

# Path to the iptables template area.
our $TEMPLATE   = '/afs/ir/service/jumpstart/data/iptables';

##############################################################################
# Installation
##############################################################################

# Return the prefix
sub prefix {
    my $data;
    ( $data = <<'END_OF_PREFIX' ) =~ s/^\s+//gm;
        *filter
        :INPUT ACCEPT
    :FORWARD ACCEPT
    :OUTPUT ACCEPT
    :SUL -
    -A INPUT -j SUL
    -A SUL -i lo -j ACCEPT
END_OF_PREFIX

    return $data;
}

# Return the suffix
sub suffix {
    my $data;
    ( $data = <<'END_OF_SUFFIX' ) =~ s/^\s+//gm;
        # Rejects all remaining connections with port-unreachable errors.

        -A SUL -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j REJECT --reject-with icmp-port-unreachable
        -A SUL -p udp -j REJECT --reject-with icmp-port-unreachable
        COMMIT
END_OF_SUFFIX

    return $data;
}
# Read in a file, processing includes as required.  Returns the contents of
# the file as an array.
sub read_iptables {
    my ($file) = @_;
    my @data;
    $file = $TEMPLATE . '/' . $file unless $file =~ m%^\.?/%;
    open my $MODULE, '<', $file or die "$0: cannot open $file: $!\n";
    local $_;
    while (<$MODULE>) {
        if (/^\s*include\s+(\S+)$/) {
            my $included = $1;
            $included = $TEMPLATE . '/' . $included
                unless $included =~ m%^\.?/%;
            if ($file eq $included) {
                die "$0: include loop in $file, line $.\n";
            }
            push (@data, "\n");
            push (@data, read_iptables ($included));
            push (@data, "\n");
        } elsif (/^\s*include\s/) {
            die "$0: malformed include line in $file, line $.\n";
        } else {
            push (@data, $_);
        }
    }
    close $MODULE;
    return @data;
}

# Write a file carefully.
# Consider using File::Temp
sub write_iptables {
    my ($file, @data) = @_;
    open my $NEW, '>', "$file.new" or die "$0: cannot create $file.new: $!\n";
    print $NEW @data           or die "$0: cannot write to $file.new: $!\n";
    close $NEW                 or die "$0: cannot flush $file.new: $!\n";
    rename ("$file.new", $file)
        or die "$0: cannot install new $file: $!\n";
}

# Install iptables on a Red Hat system.  Takes the array containing the new
# iptables data.
sub install_redhat {
    my (@data) = @_;
    write_iptables ('/etc/sysconfig/iptables', @data);
    system('/sbin/service', 'iptables', 'restart');
}

# Install iptables on a Debian system.  Take the array containing the new
# iptables data.
sub install_debian {
    my (@data) = @_;
    unless (-d '/etc/iptables') {
        mkdir ('/etc/iptables', 0755)
            or die "$0: cannot mkdir /etc/iptables: $!\n";
    }
    write_iptables ('/etc/iptables/general', @data);
    system('/sbin/iptables-restore < /etc/iptables/general');
}

##############################################################################
# Main routine
##############################################################################

# Fix things up for error reporting.
$| = 1;
my $fullpath = $0;
$0 =~ s%.*/%%;

# Parse command-line options.
my ($help, $version);
Getopt::Long::config ('bundling', 'no_ignore_case');
GetOptions ('h|help'             => \$help,
            'v|version'          => \$version) or exit 1;
if ($help) {
    print "Feeding myself to perldoc, please wait....\n";
    exec ('perldoc', '-t', $fullpath);
} elsif ($version) {
    my $version = join (' ', (split (' ', $ID))[1..3]);
    $version =~ s/,v\b//;
    $version =~ s/(\S+)$/($1)/;
    $version =~ tr%/%-%;
    print $version, "\n";
    exit;
}
my @modules;

if ( -d '/etc/iptables.d' ) {
    @modules = </etc/iptables.d/*>;
}

# Concatenate everything together.
my @data;
push (@data, prefix());
push (@data, "\n");
for my $module (@modules) {
    push (@data, read_iptables($module));
    push (@data, "\n");
}
push (@data, suffix());

if (-f '/etc/debian_version') {
    install_debian (@data);
} elsif (-f '/etc/redhat-release') {
    install_redhat (@data);
} else {
    die "$0: cannot figure out whether this is Red Hat or Debian\n";
}

exit 0;
__END__

##############################################################################
# Documentation
##############################################################################

=head1 NAME

rebuild-iptables - Construct an iptables rules file from fragments

=head1 SYNOPSIS

rebuild-iptables [B<-hv>]

=head1 DESCRIPTION

B<rebuild-iptables> constructs an iptables configuration file by concatenating
various modules found in F</etc/iptables.d>.  The resulting iptables
configuration file is written to the appropriate file for either Red Hat or
Debian (determined automatically) and iptables is restarted.

Each module is just a text file located in the directory mentioned above that
contains one or more iptables configuration lines (basically the arguments to
an B<iptables> invocation), possibly including comments.

Along with the modules in the directory specified, a standard prefix and suffix
is added.

Normally, the contents of each module are read in verbatim, but a module may
also contain the directive:

    include <module>

on a separate line, where <module> is the path to another module to include,
specified the same way as modules given on the command line (hence, either a
file name relative to F</afs/ir/service/jumpstart/data/iptables> or an
absolute path).  Such a line will be replaced with the contents of the named
file.  Be careful when using this directive to not create loops; files
including themselves will be detected, but more complex loops will not and
will result in infinite output.

=head1 OPTIONS

=over 4

=item B<-h>, B<--help>

Print out this documentation (which is done simply by feeding the script to
C<perldoc -t>).

=item B<-v>, B<--version>

Print out the version of B<rebuild-iptables> and exit.

=back

=head1 FILES

=over 4

=item F</etc/iptables.d>

The default module location.

=item F</etc/debian_version>

If this file exists, the system is assumed to be a Debian system for
determining the installation location when B<-i> is used.

=item F</etc/iptables/general>

The install location of the generated configuration file on Debian.

=item F</etc/redhat-release>

If this file exists, the system is assumed to be a Red Hat system for
determining the installation location when B<-i> is used.

=item F</etc/sysconfig/iptables>

The install location of the generated configuration file on Red Hat.

=back

=head1 AUTHOR

Russ Allbery <rra@stanford.edu>
Digant C Kasundra <digant@stanford.edu>

=head1 SEE ALSO

iptables(8)

=cut

modules/iptables/files/fragments/ftp (sample)

# FTP setup, requiring passive mode.
-A SUL -s 171.64.0.0/14 -p tcp -m tcp --dport 20 --syn -j ACCEPT
-A SUL -s 172.24.0.0/14 -p tcp -m tcp --dport 20 --syn -j ACCEPT
-A SUL -s 171.64.0.0/14 -p tcp -m tcp --dport 21 --syn -j ACCEPT
-A SUL -s 172.24.0.0/14 -p tcp -m tcp --dport 21 --syn -j ACCEPT

Discussion

Thanks for the recipe, i have taken what you have done here and modified it to be more versatile. The rebuild-iptables has been modified. this is the diff output from your original to my modified, I have also included the modified version as an attachment.

Main changes include:

  • removed the suffix dropping all traffic to a drop table
  • added type to prefix as we now handle table type (ie filter or mangle)
  • added support to have comments/whitespace to fragments
  • changed module globbing to use filename prefix of filter- or mangle- this is so we can have rules for both tables in multiple modules
  • added some documentation to reflect the filename requirements
--- rebuild-iptables        2008-07-02 10:06:43.000000000 +1000
+++ rebuild-iptables-new    2008-07-02 10:04:43.000000000 +1000
@@ -31,17 +31,24 @@

 # Return the prefix
 sub prefix {
+    my $type = shift;
     my $data;
-    ( $data = <<'END_OF_PREFIX' ) =~ s/^\s+//gm;
-        *filter
-        :INPUT ACCEPT
-    :FORWARD ACCEPT
-    :OUTPUT ACCEPT
-    :SUL -
-    -A INPUT -j SUL
-    -A SUL -i lo -j ACCEPT
+   if ( $type eq 'filter' ) {
+           ( $data = <<'END_OF_PREFIX' ) =~ s/^\s+//gm;
+*filter
+:INPUT ACCEPT
+:FORWARD ACCEPT
+:OUTPUT ACCEPT
 END_OF_PREFIX
-
+   }
+   elsif ( $type eq 'mangle' ) {
+           ( $data = <<'END_OF_PREFIX' ) =~ s/^\s+//gm;
+*nat
+:PREROUTING ACCEPT
+:POSTROUTING ACCEPT
+:OUTPUT ACCEPT
+END_OF_PREFIX
+   }
     return $data;
 }

@@ -49,10 +56,6 @@
 sub suffix {
     my $data;
     ( $data = <<'END_OF_SUFFIX' ) =~ s/^\s+//gm;
-        # Rejects all remaining connections with port-unreachable errors.
-
-        -A SUL -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j REJECT --reject-with icmp-port-unreachable
-        -A SUL -p udp -j REJECT --reject-with icmp-port-unreachable
         COMMIT
 END_OF_SUFFIX

@@ -80,7 +83,12 @@
         } elsif (/^\s*include\s/) {
             die "$0: malformed include line in $file, line $.\n";
         } else {
-            push (@data, $_);
+            # strip comments/whitespace/blank lines out of module
+            $_ =~ s/\s*#.*$//;
+            $_ =~ s/^\s*//;
+            if ( $_ !~ /^\s*$/ ) {
+                push (@data, $_);
+            }
         }
     }
     close $MODULE;
@@ -143,17 +151,30 @@
     print $version, "\n";
     exit;
 }
-my @modules;
+my ( @filter_modules, @mangle_modules );

 if ( -d '/etc/iptables.d' ) {
-    @modules = </etc/iptables.d/*>;
+    @filter_modules = </etc/iptables.d/filter-*>;
+    @mangle_modules = </etc/iptables.d/mangle-*>;
 }

 # Concatenate everything together.
 my @data;
-push (@data, prefix());
+push (@data, prefix('filter'));
 push (@data, "\n");
-for my $module (@modules) {
+for my $module (@filter_modules) {
+    push (@data, read_iptables($module));
+    push (@data, "\n");
+}
+push (@data, suffix());
+
+push (@data, prefix('mangle'));
+push (@data, "\n");
+for my $module (@mangle_modules) {
     push (@data, read_iptables($module));
     push (@data, "\n");
 }
@@ -193,6 +214,13 @@
 contains one or more iptables configuration lines (basically the arguments to
 an B<iptables> invocation), possibly including comments.

+NOTE: the module name needs to be prefixed with either filter- or mangle- . For
+example: /etc/iptables.d/filter-foo. This is required so the rules can be put
+in the appropriate table.
+
+WARNING: if the module name is not prefixed with filter- or mangle- it WILL be
+ignored.
+
 Along with the modules in the directory specified, a standard prefix and suffix
 is added.

Attachments