[lug] favorite iptables cookbook site?

Rob Nagler nagler at bivio.biz
Fri Feb 28 17:21:58 MST 2003


D. Stimits writes:
> I'm curious if anyone here has a URL for something similar to the Perl 
> Cookbook, but for iptables?

Appended is a Perl script that does a bunch of things.  You create a
file /etc/sysconfig/iptables-local.PL which it imports.  The file
looks like:

    # $Id: iptables-local.PL,v 1.3 2002/04/30 19:47:42 nagler Exp $
    #
    # Read by iptables.PL.
    #
    {
	local_pop => 1,
	smtp => 1,
	samba => 1,
	oracle => 1,
	http => 1,
    };


The iptables.PL scripts then does the right thing, e.g. figures out
the local network to set local_pop.  It needs all interfaces connected
at the time, which may be problematic for your wireless interfaces.

It trusts lo0 for everything.

Before it starts, it backs up your current iptables config
(iptables-save), and then when it finishes, it saves the new config
only if you type "yes" within 30 seconds.  Otherwise, it reverts to
your old configuration.  This is handy when you accidentally lock
yourself out. :-)

Not well-documented, but well-written Perl, of course. ;-)

Cheers,
Rob
----------------------------------------------------------------
#!/usr/bin/perl -w
# $Id: iptables.PL,v 1.17 2003/02/24 03:37:49 tvilot Exp $
use strict;

=head1 NAME

iptables.PL - initialize iptables using configuration

=head1 SYNOPSIS

  perl -w iptables.PL [-verbose] [-noexecute]

=head1 DESCRIPTION

C<iptables.PL> initializes iptables.  You run this once
and then use C<iptables-save>.

I got some of this from: http://www.cs.princeton.edu/~jns/security/iptables/

We should have an error trap on this.  You should not debug this script
when you don't have access to computer locally.

=head1 CONFIGURATION

Configuration is a hash in F</etc/sysconfig/iptables-local.PL> (need not be
present) with the following boolean attributes:

=over 4

=item dns

Allow incoming dns from anywhere.

=item http

Allow incoming http and https from anywhere.

=item ftp

Allow incoming ftp from anywhere.

=item cvs

Allow incoming cvs from anywhere.

=item http_proxy : array_ref

Allow http proxy (3128) from these nets

=item local_dns

Allow incoming dns from local net only.

=item local_pop

Allow incoming pop and imap from local net only.

=item oracle

Allow incoming oracle (1521) on local net only.

=item pop

Allow incoming pop and imap from anywhere.

=item samba

Allow incoming samba on local net only.

=back

=cut

#=IMPORTS

#=VARIABLES
my($_VERBOSE) = grep($_ =~ /^-(v|verbose)$/, @ARGV);
my($_NO_EXECUTE) = grep($_ =~ /^-(n|noexecute)$/, @ARGV) && ($_VERBOSE = 1);
my($_CFG) = do('/etc/sysconfig/iptables-local.PL') || {};
my($_LOCAL_NET) = _local_net();
my($_ETH) = _eth();
_main();

#=PRIVATE METHODS

# _basic_filter(string chain, string dir, array accept_interfaces)
#
# accept_interfaces are not filtered.  On the rest of the interfaces,
# allow established,related and drop invalid.
#
sub _basic_filter {
    my($chain, $dir, @accept_interfaces) = @_;
    foreach my $ifc (@accept_interfaces) {
    	_do("-A $chain $dir $ifc -j ACCEPT");
    }

    # Basic protection
    _do("-A $chain -m state --state ESTABLISHED,RELATED -j ACCEPT");
    _stop_and_log($chain, '-m state --state INVALID', 'DROP', 'invalid');
    return;
}

# _cfg_forward()
#
# Forward filter is completely closed.
#
sub _cfg_forward {
    _basic_filter('FORWARD', '-i', 'lo');
    _stop_and_log('FORWARD', '', '', 'default');
    return;
}

# _cfg_input()
#
# INPUT filter
#
sub _cfg_input {
    # lo is a safe interface.  We accept anything from it.
    _basic_filter('INPUT', '-i', 'lo');
    _cfg_input_tcp();
    _cfg_input_udp();
    _cfg_input_eth('-p icmp -j ACCEPT');
    _stop_and_log('INPUT', '', '', 'default');
    return;
}

# _cfg_input_eth(string args)
#
# shorthand for input -i eth commands.
#
sub _cfg_input_eth {
    my($args) = @_;
    foreach my $eth (@$_ETH) {
	_do("-A INPUT -i $eth $args");
    }
    return;
}

# _cfg_input_tcp()
#
# Input TCP ports
# Added option for ftp (tjv)
#
sub _cfg_input_tcp {
    my($ports) = '';
    $ports .= ',http,https' if $_CFG->{http};
    $ports .= ',smtp' if $_CFG->{smtp};
    $ports .= ',20,21' if $_CFG->{ftp};
    $ports .= ',2401' if $_CFG->{cvs};
    $ports .= ',pop3,pop3s,imap,imap3,imaps' if $_CFG->{pop};
    _cfg_input_eth('-p tcp -m state --state NEW -m multiport'
	. " --destination-port ssh$ports"
	. ' -j ACCEPT');
    _cfg_input_eth('-p tcp -m state --state NEW --destination-port 8000:8999'
        . ' -j ACCEPT');
    $ports = '';
    $ports .= 'netbios-ns,netbios-dgm,netbios-ssn,' if $_CFG->{samba};
    $ports .= '1521,' if $_CFG->{oracle};
    $ports .= 'pop3,pop3s,imap,imap3,imaps' if $_CFG->{local_pop};
    chop($ports);
    _cfg_input_eth('-p tcp -m state --state NEW -m multiport'
        . " -s $_LOCAL_NET -d $_LOCAL_NET --destination-port $ports"
        . ' -j ACCEPT')
	if $ports;

    foreach my $net (@{$_CFG->{http_proxy} || []}) {
	_cfg_input_eth('-p tcp -m state --state NEW -m multiport'
	    . " -s $net --destination-port 3128"
	    . ' -j ACCEPT');
    }

    # Reject ident probes with a tcp reset.  Somef servers won't do well if you
    # don't do this.
    _cfg_input_eth('-p tcp --dport auth -j REJECT --reject-with tcp-reset');

    # LOG port scans
    _stop_and_log('INPUT',
	'-p tcp --tcp-flags SYN,ACK,RST ACK -m state --state NEW',
	'REJECT --reject-with tcp-reset',
	'ACK scan');

    # We'll see this with trace route
    _stop_and_log('INPUT',
	'-p tcp ! --syn -m state --state NEW',
	'DROP',
	'scan');
    _stop_and_log('INPUT',
	'-p tcp --tcp-option 64',
	'DROP',
	'tcpopt 64');
    _stop_and_log('INPUT',
	'-p tcp --tcp-option 128',
	'DROP',
	'tcpopt 128');
    return;
}

# _cfg_input_udp()
#
# INPUT UDP configuration
#
sub _cfg_input_udp {
    if ($_CFG->{dns}) {
	die('only one of dns and local_dns may be specified')
	    if $_CFG->{local_dns};
        _cfg_input_eth('-p udp --destination-port domain'
	    . ' -j ACCEPT');
    }
    else {
        _cfg_input_eth("-p udp --destination-port domain -s $_LOCAL_NET"
	    . ' -j ACCEPT')
	    if $_CFG->{local_dns};
	_cfg_input_eth('-p udp --source-port domain -m state'
	    . ' --state ESTABLISHED -j ACCEPT');
    }
    _cfg_input_eth('-p udp --source-port ntp -m state'
	. ' --state ESTABLISHED -j ACCEPT');
    _cfg_input_eth('-p udp  -m multiport --port bootpc,bootps -j ACCEPT');
    _cfg_input_eth('-p udp -m multiport'
	. " -s $_LOCAL_NET -d $_LOCAL_NET --destination-port"
	. ' netbios-ns,netbios-dgm,netbios-ssn'
	. ' -j ACCEPT')
	if $_CFG->{samba};
    # Ignore route packets
    _do('-A INPUT -p udp --dport route -j DROP');
    return;
}

# _cfg_output()
#
# Output filter is wide open for now.
#
sub _cfg_output {
    _basic_filter('OUTPUT', '-o', 'lo', @$_ETH);
    _stop_and_log('OUTPUT', '', '', 'default');
    return;
}

# _do(string cmd)
#
# Dies if system($cmd) doesn't work.  Inserts iptables in prefix if $cmd
# begins with '-'.   Observes noexecute and verbose flags.
#
sub _do {
    my($cmd) = @_;
    $cmd = 'iptables '.$cmd if $cmd =~ /^-/;
    print($cmd, "\n") if $_VERBOSE;
    return if $_NO_EXECUTE;
    system($cmd) == 0 || die("$cmd FAILED");
    return;
}

# _eth() : array_ref
#
# Returns all ethernet interfaces.
#
sub _eth {
    my($res) = [];
    foreach my $ifc (split(/\n\n/, `ifconfig -a`)) {
	push(@$res, $1)
	    if $ifc =~ /^(\S+)\s.*Ethernet.*\bUP\b/s;
    }
    die("No ethernet interfaces found\n") unless @$res;
    print("Ethernet interfaces: @$res\n");
    return $res;
}

# _init_modules()
#
# Loade modules needed by iptables
#
sub _init_modules {
    foreach my $mod (qw(
	ip_tables
	iptable_nat
	ip_conntrack
	ip_conntrack_ftp
	ip_conntrack_irc
	iptable_filter
	iptable_mangle
	ipt_LOG
	ipt_REJECT
	ipt_state)) {
        _do("modprobe $mod");
    }
    return;
}

# _init_tables()
#
# initializes the policies.
#
sub _init_tables {
    #
    # POLICY (before flush): DROP
    #
    _do('-P INPUT DROP');
    _do('-P OUTPUT DROP');
    _do('-P FORWARD DROP');

    #
    # FLUSH
    #
    _do('-F');
    # _do('-F -t nat');
    # _do('-F -t mangle');
    _do('-X');
    _do('-Z');

    #
    # POLICY (after flush): DROP
    #
    _do('-P INPUT DROP');
    _do('-P OUTPUT DROP');
    _do('-P FORWARD DROP');
    return;
}

# _local_net() : string
#
# Determine the local network and mask
#
sub _local_net {
    foreach my $line (split(/\n/, `netstat -r`)) {
	return "$1/$2"
	    if $line =~ /^((?:\d+\.){3}\d+)\s*\S*\s*(255.255.25\d.\d+)/;
    }
    die('unable to determine local network');
    # DOES NOT RETURN
}

# _main()
#
# Initializes then executes configurations
#
sub _main {
    my(@ARGV) = @_;
    unless (_yes(10, 'Reconfigure iptables?')) {
	print("\nNOT CONFIGURING\n");
	return;
    }
    my($save) = '/etc/sysconfig/iptables';
    _do('/etc/rc.d/init.d/iptables save') unless -f $save;
    my($old) = "$save.rpmsave";
    _do("cp -pf $save $old");
    eval {
	_init_modules();
	_init_tables();
	_cfg_input();
	_cfg_output();
	_cfg_forward();
	# minimize counter differences in diff
	_do('-Z');
	_do('/etc/rc.d/init.d/iptables save');
	print(grep(s/^([<>] )\[\d+:\d+] /$1/, `diff $old $save`));
	# Give more time to review changes
	die("not OK\n") unless _yes(30, 'Reconfiguration OK?')
    };
    return unless $@;
    print(STDERR $@, "RESTORING OLD CONFIGURATION\n");
    # If the copy fails, we have to blow up
    _do("cp -pf $old $save");
    _do('/etc/rc.d/init.d/iptables restart');
    return;
}

# _stop_and_log(string chain, string rule, string jump string log_prefix) : 
#
# Log the rule on chain with prefix and rewrite rule if jump is not
# empty.
#
sub _stop_and_log {
    my($chain, $rule, $jump, $log_prefix) = @_;
    _do("-A $chain $rule -m limit --limit 2/m --limit-burst 1"
	. " -j LOG --log-prefix '$chain $log_prefix: '");
    _do("-A $chain $rule -j $jump") if $jump;
    return;
}

# _yes(int timeout, string prompt) : boolean
#
# Returns true if ok to proceed.  Times out if no answer in 15 seconds.
#
sub _yes {
    my($timeout, $prompt) = @_;
    print($prompt, "  You have $timeout seconds to enter y[es]: ");
    my($answer);
    eval {
	local($SIG{ALRM}) = sub { die("alarm\n"); };
	alarm($timeout);
	$answer = <STDIN>;
	alarm(0);
    };
    return 0 unless $answer;
    chomp($answer);
    $answer =~ s/^\s+|\s+$//g;
    return $answer =~ /^(y|yes)$/ ? 1 : 0;
}

=head1 VERSION

$Id: iptables.PL,v 1.17 2003/02/24 03:37:49 tvilot Exp $

=cut






More information about the LUG mailing list