shorewall_code/docs/ManualChains.xml

214 lines
7.6 KiB
XML
Raw Normal View History

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd">
<article>
<!--$Id$-->
<articleinfo>
<title>Manual Chains</title>
<authorgroup>
<author>
<firstname>Tom</firstname>
<surname>Eastep</surname>
</author>
</authorgroup>
<pubdate><?dbtimestamp format="Y/m/d"?></pubdate>
<copyright>
<year>2008</year>
<year>2009</year>
<holder>Thomas M. Eastep</holder>
</copyright>
<legalnotice>
<para>Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License, Version
1.2 or any later version published by the Free Software Foundation; with
no Invariant Sections, with no Front-Cover, and with no Back-Cover
Texts. A copy of the license is included in the section entitled
<quote><ulink url="GnuCopyright.htm">GNU Free Documentation
License</ulink></quote>.</para>
</legalnotice>
</articleinfo>
<section id="Intro">
<title>Introduction</title>
<para>For Perl programmers, manual chains provide an alternative to
Actions with extension scripts. Manual chains are chains which you create
and populate yourself using the low-level functions in
Shorewall::Chains.</para>
<para>Manual chains work in conjunction with the
<firstterm>compile</firstterm> <ulink
url="shorewall_extension_scripts.htm">extension script</ulink> and <ulink
url="configuration_file_basics.html#Embedded">Embedded PERL
scripts</ulink>. The general idea is like this:</para>
<itemizedlist>
<listitem>
<para>In the compile extension script, you define functions that you
can call later using Embedded PERL. These functions create a
<firstterm>manual chain</firstterm> using
Shorewall::Chains::new_manual_chain() and populate it with rules using
2011-01-31 16:07:10 +01:00
Shorewall::Chains::add_rule(). The name passed to new_manual_chain()
must not be longer than 29 characters.</para>
</listitem>
<listitem>
<para>The functions also call Shorewall::Config::shorewall() to create
and pass a rule to Shorewall. The TARGET in that rule is the name of
the chain just created.</para>
</listitem>
<listitem>
<para>The functions defined in the compile script are called by
embedded PERL statements. The arguments to those calls define the
contents of the manual chains and the rule(s) passed back to Shorewall
for normal processing.</para>
</listitem>
</itemizedlist>
</section>
<section id="Example">
<title>Example</title>
<para>This example provides an alternative to the <ulink
url="PortKnocking.html">Port Knocking</ulink> example.</para>
<para>In this example, a Knock.pm module is created and placed in
/etc/shorewall:</para>
<programlisting>package Knock;
use strict;
use warnings;
use base qw{Exporter};
use Carp;
use Shorewall::Chains;
use Scalar::Util qw{reftype};
use Shorewall::Config qw{shorewall};
our @EXPORT = qw{Knock};
my %recent_names;
my %chains_created;
sub scalar_or_array {
my $arg = shift;
my $name = shift;
return () unless defined $arg;
return ($arg) unless reftype($arg);
return @$arg if reftype($arg) eq 'ARRAY';
croak "Expecting argument '$name' to be scalar or array ref";
}
sub Knock {
my $src = shift;
my $dest = shift;
my $args = shift;
my $proto = $args-&gt;{proto} || 'tcp';
my $seconds = $args-&gt;{seconds} || 60;
my $original_dest = $args-&gt;{original_dest} || '-';
my @target = scalar_or_array($args-&gt;{target}, 'target');
my @knocker_ports = scalar_or_array($args-&gt;{knocker}, 'knocker');
my @trap_ports = scalar_or_array($args-&gt;{trap}, 'trap');
if (not defined $args-&gt;{name}) {
# If you don't supply a name, then this must be the single-call
# variant, so you have to specify all the arguments
unless (scalar @target) {
croak "No 'target' ports specified";
}
unless (scalar @knocker_ports) {
croak "No 'knock' ports specified";
}
}
# We'll need a unique name for the recent match list. Construct one
# from the port and a serial number, if the user didn't supply one.
my $name = $args-&gt;{name} || ($target[0] . '_' . ++$recent_names{$target[0]});
$name = 'Knock' . $name;
# We want one chain for all Knock rules that share a 'name' field
my $chainref = $chains_created{$name};
unless (defined $chainref) {
$chainref = $chains_created{$name} = new_manual_chain($name);
}
# Logging
if ($args-&gt;{log_level}) {
foreach my $port (@target) {
log_rule_limit($args-&gt;{log_level},
$chainref,
'Knock',
'ACCEPT',
'',
$args-&gt;{log_tag} || '',
'add',
"-p $proto --dport $port -m recent --rcheck --name $name"
);
log_rule_limit($args-&gt;{log_level},
$chainref,
'Knock',
'DROP',
'',
$args-&gt;{log_tag} || '',
'add',
"-p $proto --dport ! $port"
);
}
}
# Add the recent match rules to the manual chain
foreach my $knock (@knocker_ports) {
add_rule($chainref, "-p $proto --dport $knock -m recent --name $name --set -j DROP");
}
foreach my $trap (@trap_ports) {
add_rule($chainref, "-p $proto --dport $trap -m recent --name $name --remove -j DROP");
}
foreach my $port (@target) {
add_rule($chainref, "-p $proto --dport $port -m recent --rcheck --seconds $seconds --name $name -j ACCEPT");
}
# And add a rule to the main chain(s) to jump into the manual chain at the appropriate points
my $all_dest_ports = join(',', @target, @knocker_ports, @trap_ports);
shorewall "$chainref-&gt;{name} $src $dest $proto $all_dest_ports - $original_dest";
return 1;
}
1;</programlisting>
<para>This simplifies /etc/shorewall/compile:<programlisting>use Knock;
1;</programlisting></para>
<para>The rule from the Port Knocking article:</para>
<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S)
SSHKnock net $FW tcp 22,1599,1600,1601
</programlisting>
<para>becomes:<programlisting>PERL Knock 'net', '$FW', {target =&gt; 22, knocker =&gt; 1600, trap =&gt; [1599, 1601]};</programlisting>Similarly<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S) SOURCE ORIGINAL
# PORT(S) DEST
DNAT- net 192.168.1.5 tcp 22 - 206.124.146.178
SSHKnock net $FW tcp 1599,1600,1601
SSHKnock net loc:192.168.1.5 tcp 22 - 206.124.146.178</programlisting>becomes:<programlisting>#ACTION SOURCE DEST PROTO DEST PORT(S) SOURCE ORIGINAL
# PORT(S) DEST
DNAT- net 192.168.1.5 tcp 22 - 206.124.146.178
PERL Knock 'net', '$FW', {name =&gt; 'SSH', knocker =&gt; 1600, trap =&gt; [1599, 1601]};
PERL Knock 'net', 'loc:192.168.1.5', {name =&gt; 'SSH', target =&gt; 22, original_dest =&gt; '206.124.136.178'};</programlisting></para>
</section>
</article>