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. You will also need to create a modules/iptables/files/empty directory to force the purge attribute to work as expected, ie deleting no longer used 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,
recurse => true,
force => true,
source => "puppet:///iptables/empty",
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
# Adapted by Digant C Kasundra
# 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 = ;
}
# 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 constructs an iptables configuration file by concatenating
various modules found in F. 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 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
on a separate line, where 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 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).
=item B<-v>, B<--version>
Print out the version of B and exit.
=back
=head1 FILES
=over 4
=item F
The default module location.
=item F
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
The install location of the generated configuration file on Debian.
=item F
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
The install location of the generated configuration file on Red Hat.
=back
=head1 AUTHOR
Russ Allbery
Digant C Kasundra
=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 = ;
+ @filter_modules = ;
+ @mangle_modules = ;
}
# 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 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.
Alternate method¶
Here is a simpler alternative so far tested on Debian. It simply copies a rules file (“hostname.rules”) into place and refreshes iptables.
class iptables {
package {
"iptables-persistent": ensure => present;
}
service { "iptables-persistent":
require => Package["iptables-persistent"],
# Because there is no running process for this service, the normal status
# checks fail. Because puppet then thinks the service has been manually
# stopped, it won't restart it. This fake status command will trick puppet
# into thinking the service is *always* running (which in a way it is, as
# iptables is part of the kernel.)
hasstatus => true,
status => "true",
# Under Debian, the "restart" parameter does not reload the rules, so tell
# Puppet to fall back to stop/start, which does work.
hasrestart => false,
}
file {
"/etc/iptables/rules":
owner => "root",
group => "root",
mode => 640,
source => "puppet:///modules/iptables/$hostname.rules",
require => Package["iptables-persistent"],
# When this file is updated, make sure the rules get reloaded.
notify => Service["iptables-persistent"],
;
}
}