Commit b21e6942 authored by Mike Hibler's avatar Mike Hibler
Browse files

Firewall support part III: client scripts.

Overview of simply firewall setup.

Experimentor specifies in their ns file:

     set fw [new Firewall $ns]
     $fw style <open|closed|basic>

to set up an "open" ("allow any"), "closed" ("deny any"), or "basic"
(allow ICMP and ssh) firewall.  "basic is the default.  Additional rules
can be added with:

     $fw add-rule <IPFW format rule>
     $fw add-numbered-rule <1-50000> <IPFW format rule>

where the former implicitly numbers rules such that the firewall processes
them in the order given in the NS file.  The latter allows explicit
specification of the numbering.  Currently the rules are fixed strings,
there is no variable substitution.  There is also no syntax checking done
on the rules at parse time.

We allocate an extra node to the experiment to serve as a firewall.
Currently that node runs FreeBSD and uses IPFW.  In the initial configuration,
all other nodes in the experiment will just be setup with a default route
that points to the firewall node.  So all outbound traffic will pass through
it.  Inbound traffic will still travel straight to the node.  This should
prevent nodes from accidentally initiating attacks on the outside world.
Long term we will of course enforce the firewall on all traffic, that should
not have any effect on the NS syntax above.

When a node boots, there will be an rc.firewall script that checks to see
if there is a firewall for the experiment and if so, which node it is.
This is done with the TMCD "firewallinfo" command which returns:

      TYPE=none

      TYPE=remote FWIP=N.N.N.N

      TYPE=<fwtype> STYLE=<fwstyle> IN_IF=<macaddr> OUT_IF=<macaddr>
      RULENO=<num> RULE="<ipfw command string>"
      RULENO=...
      ...

In the case of no firewall we get back TYPE=none, and we continue as normal.
Otherwise, there are two types of replies, one for a node that is being
firewalled (TYPE=remote) and one for a node that is a firewall
(TYPE=<fwtype> + RULES).

In the TYPE=remote case, the firewall node indicated by FWIP.  This is
the address we use for the default route.

For TYPE=<fwtype>, we are the firewall, and we get STYLE and IN_IF/OUT_IF
info.  Here TYPE indicates whether we should use ipfw or whatever.
For now it is always ipfw.  IN_IF and OUT_IF may someday indicate the
interfaces to use for the internal and external connections, right now
both will indicate the control net interface.  So, after ensuring that
the ipfw modules is loaded, we grab the provided RULE info, which includes
both per-experiment and default rules, and setup ipfw.

Issues to resolve:
       - synchronization: how to ensure firewall comes up first
       - how to better implement the firewalling
         (i.e., without the cooperation of the nodes)
       - support the equiv of linkdelays (on-node firewalling)?
       - allow firewalls within experiments?
         (ie., on experimental interfaces)
       - dynamic changing of firewall rules via events?
       - how to show firewall state in various web pages
parent fa3d2fa7
...@@ -22,7 +22,7 @@ SCRIPTS = $(addprefix $(SRCDIR)/, \ ...@@ -22,7 +22,7 @@ SCRIPTS = $(addprefix $(SRCDIR)/, \
rc.tunnels rc.ifconfig rc.delays rc.hostnames \ rc.tunnels rc.ifconfig rc.delays rc.hostnames \
rc.syncserver rc.linkagent \ rc.syncserver rc.linkagent \
rc.keys rc.trafgen rc.tarfiles rc.rpms rc.progagent \ rc.keys rc.trafgen rc.tarfiles rc.rpms rc.progagent \
rc.startcmd rc.simulator rc.topomap) rc.startcmd rc.simulator rc.topomap rc.firewall)
include $(OBJDIR)/Makeconf include $(OBJDIR)/Makeconf
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use Getopt::Std;
#
# Hosts we need un-firewalled static routes for
#
my $elvinip = "224.4.0.1";
my $viproutes = "boss ops fs $elvinip";
my $action = "boot";
my $debug = 0;
my $fwinfo;
my @fwrules;
sub usage()
{
print "Usage: " .
scriptname() . "boot|shutdown|reconfig|reset\n";
exit(1);
}
# Turn off line buffering on output
$| = 1;
# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
#
# Load the OS independent support library. It will load the OS dependent
# library and initialize itself.
#
use libsetup;
use liblocsetup;
use libtmcc;
use librc;
#
# Must be root.
#
if ($UID != 0) {
die("*** $0:\n".
" Must be root to run this script!\n");
}
#
# Only local cluster nodes right now
#
exit(0)
if (MFS() || REMOTE() || SIMHOST() || JAILHOST());
# Protos.
sub doboot();
sub doshutdown();
sub doreconfig();
sub docleanup();
sub firewaller();
sub firewallee();
# XXX for testing
#configtmcc("portnum", 7778);
#
# XXX for debugging default rules: make sure we always fetch
# from boss so we can pick up changes without rebooting using
# "/usr/local/etc/emulab/rc/rc.firewall reconfig"
#
configtmcc("nocache", 1);
# Allow default above.
if (@ARGV) {
$action = $ARGV[0];
}
my $TMFWC = CONFDIR() . "/rc.fw";
# Execute the action.
SWITCH: for ($action) {
/^boot$/i && do {
doboot();
last SWITCH;
};
/^shutdown$/i && do {
doshutdown();
last SWITCH;
};
/^reconfig$/i && do {
doreconfig();
last SWITCH;
};
/^reset$/i && do {
docleanup();
last SWITCH;
};
fatal("Invalid action: $action\n");
}
exit(0);
sub doboot()
{
#
# Get our firewall info
#
if (getfwconfig(\$fwinfo, \@fwrules) != 0) {
exit(0);
}
print "Setting up firewall\n";
#
# Create the rc.firewall script
#
open(FWC, ">$TMFWC") or
fatal("Could not open $TMFWC: $!\n");
print FWC "#!/bin/sh\n";
print FWC "# auto-generated by $BINDIR/rc/rc.firewall, DO NOT EDIT\n";
print FWC "if [ x\$1 = x ]; then action=enable; else action=\$1; fi\n";
if ($fwinfo->{TYPE} eq "none") {
print "No firewall\n"
if ($debug);
exit(0);
}
#
# If TYPE is not "remote", we ourselves are a firewall
#
my $rc = 0;
SWITCH: for ($fwinfo->{TYPE}) {
/^ipfw$/i && do {
$rc = firewaller();
last SWITCH;
};
/^remote$/i && do {
last SWITCH;
};
fatal("Invalid firewall type: $fwinfo->{TYPE}\n");
}
#
# If FWIP is set, we are the "client" of another firewall
#
if ($rc == 0 && defined($fwinfo->{FWIP})) {
$rc = firewallee();
}
close(FWC);
if ($rc) {
unlink($TMFWC);
exit($rc);
}
#
# and execute it!
#
chmod(0755, $TMFWC);
system("$TMFWC enable");
if ($?) {
fatal("Error running $TMFWC");
}
}
sub firewaller()
{
my ($upline, $downline) = os_fwconfig_line($fwinfo, @fwrules);
print FWC "case \"\$action\" in\n";
print FWC " enable)\n";
print FWC " $upline\n";
print FWC " ;;\n";
print FWC " disable)\n";
print FWC " $downline\n";
print FWC " ;;\n";
print FWC " *)\n";
print FWC " echo \"Usage: rc.firewall {enable|disable}\"\n";
print FWC " exit 1\n";
print FWC " ;;\n";
print FWC "esac\n";
print FWC "exit 0\n";
return 0;
}
#
# Configuration for a firewalled node.
#
# With a transparent firewall, there would be nothing to do here.
#
# However, in our "all volunteer" firewall model, we must replace our
# default route with one leading to the firewall node. Before doing that,
# we ensure that we have static routes for boss, ops, fs, elvind, and the
# firewall node.
#
sub firewallee()
{
my $orouter = `cat $BOOTDIR/routerip`;
chomp($orouter);
#
# Assume the firewall itself is on a local interface with an already
# existing interface route. Thus we don't need to force a route.
#
#$viproutes .= " $fwinfo->{FWIP}";
my ($upline, $downline) =
os_fwrouteconfig_line($orouter, $fwinfo->{FWIP}, $viproutes);
my $addroute =
os_routing_add_manual("default", undef, undef, "\$nrouter", undef);
my $delroute =
os_routing_del_manual("default", undef, undef, undef, undef);
print FWC "case \"\$action\" in\n";
print FWC " enable)\n";
print FWC " $upline\n";
print FWC " cp -p $BOOTDIR/routerip $BOOTDIR/orouterip\n";
print FWC " nrouter=$fwinfo->{FWIP}\n";
print FWC " ;;\n";
print FWC " disable)\n";
print FWC " nrouter=\`cat $BOOTDIR/orouterip\`\n";
print FWC " ;;\n";
print FWC " *)\n";
print FWC " echo \"Usage: rc.firewall {enable|disable}\"\n";
print FWC " exit 1\n";
print FWC " ;;\n";
print FWC "esac\n";
print FWC "$delroute >/dev/null 2>&1\n";
print FWC "$addroute || {\n";
print FWC " echo \"Could not establish new default route\"\n";
print FWC " exit 1\n";
print FWC "}\n";
print FWC "echo \$nrouter >$BOOTDIR/routerip\n";
print FWC "case \"\$action\" in\n";
print FWC " enable)\n";
print FWC " ;;\n";
print FWC " disable)\n";
print FWC " $downline\n";
print FWC " ;;\n";
print FWC "esac\n";
print FWC "exit 0\n";
return 0;
}
#
# Shutdown Action.
#
sub doshutdown()
{
# Bring all interfaces down.
if (-e $TMFWC) {
system("$TMFWC disable");
if ($?) {
fatal("Error running $TMFWC");
}
}
}
#
# Node Reconfig Action (without rebooting).
#
sub doreconfig()
{
doshutdown();
return doboot();
}
#
# Node cleanup action (node is reset to completely clean state).
#
sub docleanup()
{
unlink $TMFWC;
}
...@@ -19,7 +19,7 @@ use Exporter; ...@@ -19,7 +19,7 @@ use Exporter;
check_nickname bootsetup startcmdstatus whatsmynickname check_nickname bootsetup startcmdstatus whatsmynickname
TBBackGround TBForkCmd vnodejailsetup plabsetup vnodeplabsetup TBBackGround TBForkCmd vnodejailsetup plabsetup vnodeplabsetup
jailsetup dojailconfig findiface libsetup_getvnodeid jailsetup dojailconfig findiface libsetup_getvnodeid
ixpsetup libsetup_refresh gettopomap ixpsetup libsetup_refresh gettopomap getfwconfig
TBDebugTimeStamp TBDebugTimeStampsOn TBDebugTimeStamp TBDebugTimeStampsOn
...@@ -836,6 +836,73 @@ sub gettunnelconfig($) ...@@ -836,6 +836,73 @@ sub gettunnelconfig($)
return 0; return 0;
} }
#
# Return the firewall configuration. We parse tmcd output here and return
# a list of hash entries to the caller.
#
sub getfwconfig($$)
{
my ($infoptr, $rptr) = @_; # Return info and rule list to caller.
my @tmccresults = ();
my $fwinfo = {};
my @fwrules = ();
$$infoptr = undef;
@$rptr = ();
if (tmcc(TMCCCMD_FIREWALLINFO, undef, \@tmccresults) < 0) {
warn("*** WARNING: Could not get firewall info from server!\n");
return -1;
}
my $rempat = q(TYPE=remote FWIP=([0-9\.]*));
my $fwpat = q(TYPE=(\w+) STYLE=(\w+) IN_IF=(\w*) OUT_IF=(\w*));
my $rpat = q(RULENO=(\d*) RULE="(.*)");
foreach my $line (@tmccresults) {
if ($line =~ /TYPE=(\w+)/) {
my $type = $1;
if ($type eq "none") {
$fwinfo->{"TYPE"} = $type;
$$infoptr = $fwinfo;
return 0;
}
if ($line =~ /$rempat/) {
my $fwip = $1;
$fwinfo->{"TYPE"} = "remote"
if (!defined($fwinfo->{"TYPE"}));
$fwinfo->{"FWIP"} = $fwip;
} elsif ($line =~ /$fwpat/) {
my $style = $2;
my $inif = $3;
my $outif = $4;
$fwinfo->{"TYPE"} = $type;
$fwinfo->{"STYLE"} = $style;
$fwinfo->{"IN_IF"} = $inif;
$fwinfo->{"OUT_IF"} = $outif;
} else {
warn("*** WARNING: Bad firewall info line: $line\n");
}
} elsif ($line =~ /$rpat/) {
my $ruleno = $1;
my $rule = $2;
my $fw = {};
$fw->{"RULENO"} = $ruleno;
$fw->{"RULE"} = $rule;
push(@fwrules, $fw);
} else {
warn("*** WARNING: Bad firewall info line: $line\n");
}
}
$$infoptr = $fwinfo;
@$rptr = @fwrules;
return 0;
}
# #
# All we do is store it away in the file. This makes it avail later. # All we do is store it away in the file. This makes it avail later.
# #
......
...@@ -27,6 +27,7 @@ use Exporter; ...@@ -27,6 +27,7 @@ use Exporter;
TMCCCMD_PROGRAMS TMCCCMD_SYNCSERVER TMCCCMD_KEYHASH TMCCCMD_NODEID TMCCCMD_PROGRAMS TMCCCMD_SYNCSERVER TMCCCMD_KEYHASH TMCCCMD_NODEID
TMCCCMD_NTPINFO TMCCCMD_NTPDRIFT TMCCCMD_EVENTKEY TMCCCMD_ROUTELIST TMCCCMD_NTPINFO TMCCCMD_NTPDRIFT TMCCCMD_EVENTKEY TMCCCMD_ROUTELIST
TMCCCMD_ROLE TMCCCMD_RUSAGE TMCCCMD_WATCHDOGINFO TMCCCMD_HOSTKEYS TMCCCMD_ROLE TMCCCMD_RUSAGE TMCCCMD_WATCHDOGINFO TMCCCMD_HOSTKEYS
TMCCCMD_FIREWALLINFO
); );
# Must come after package declaration! # Must come after package declaration!
...@@ -152,6 +153,7 @@ my %commandset = ...@@ -152,6 +153,7 @@ my %commandset =
"rusage" => {TAG => "rusage"}, "rusage" => {TAG => "rusage"},
"watchdoginfo" => {TAG => "watchdoginfo"}, "watchdoginfo" => {TAG => "watchdoginfo"},
"hostkeys" => {TAG => "hostkeys"}, "hostkeys" => {TAG => "hostkeys"},
"firewallinfo" => {TAG => "firewallinfo"},
); );
# #
...@@ -195,6 +197,7 @@ sub TMCCCMD_ROLE() { $commandset{"role"}->{TAG}; } ...@@ -195,6 +197,7 @@ sub TMCCCMD_ROLE() { $commandset{"role"}->{TAG}; }
sub TMCCCMD_RUSAGE() { $commandset{"rusage"}->{TAG}; } sub TMCCCMD_RUSAGE() { $commandset{"rusage"}->{TAG}; }
sub TMCCCMD_WATCHDOGINFO(){ $commandset{"watchdoginfo"}->{TAG}; } sub TMCCCMD_WATCHDOGINFO(){ $commandset{"watchdoginfo"}->{TAG}; }
sub TMCCCMD_HOSTKEYS() { $commandset{"hostkeys"}->{TAG}; } sub TMCCCMD_HOSTKEYS() { $commandset{"hostkeys"}->{TAG}; }
sub TMCCCMD_FIREWALLINFO(){ $commandset{"firewallinfo"}->{TAG}; }
# #
# Caller uses this routine to set configuration of this library # Caller uses this routine to set configuration of this library
......
...@@ -136,6 +136,16 @@ sub doboot() ...@@ -136,6 +136,16 @@ sub doboot()
} }
} }
#
# Get the firewall up
#
if (-x "$RCDIR/rc.firewall") {
system("$RCDIR/rc.firewall");
if ($?) {
fatal("Error running $RCDIR/rc.firewall");
}
}
if (-x "$BINDIR/tbshutdown") { if (-x "$BINDIR/tbshutdown") {
print("Starting up shutdown notification daemon\n"); print("Starting up shutdown notification daemon\n");
system("$BINDIR/tbshutdown"); system("$BINDIR/tbshutdown");
......
...@@ -21,6 +21,7 @@ use Exporter; ...@@ -21,6 +21,7 @@ use Exporter;
os_routing_enable_forward os_routing_enable_gated os_routing_enable_forward os_routing_enable_gated
os_routing_add_manual os_routing_del_manual os_homedirdel os_routing_add_manual os_routing_del_manual os_homedirdel
os_groupdel os_getnfsmounts os_groupdel os_getnfsmounts
os_fwconfig_line os_fwrouteconfig_line
); );
# Must come after package declaration! # Must come after package declaration!
...@@ -173,7 +174,7 @@ sub os_ifconfig_line($$$$$$$;$$) ...@@ -173,7 +174,7 @@ sub os_ifconfig_line($$$$$$$;$$)
if ($aliases ne "") { if ($aliases ne "") {
# Must do this first to avoid lo0 routes. # Must do this first to avoid lo0 routes.
$uplines .= "\n ". $uplines .= "\n ".
"sysctl -w net.link.ether.inet.useloopback=0\n"; "sysctl net.link.ether.inet.useloopback=0\n";
foreach my $alias (split(',', $aliases)) { foreach my $alias (split(',', $aliases)) {
my $ifalias = sprintf($IFALIAS, $iface, $alias); my $ifalias = sprintf($IFALIAS, $iface, $alias);
...@@ -398,9 +399,9 @@ sub os_routing_enable_forward() ...@@ -398,9 +399,9 @@ sub os_routing_enable_forward()
$cmd = "# IP forwarding is enabled outside the jail"; $cmd = "# IP forwarding is enabled outside the jail";
} else { } else {
# No Fast Forwarding when operating with linkdelays. # No Fast Forwarding when operating with linkdelays.
$cmd = "sysctl -w net.inet.ip.forwarding=1\n" . $cmd = "sysctl net.inet.ip.forwarding=1\n" .
" if [ ! -e $fname ]; then\n" . " if [ ! -e $fname ]; then\n" .
" sysctl -w net.inet.ip.fastforwarding=1\n" . " sysctl net.inet.ip.fastforwarding=1\n" .
" fi\n"; " fi\n";
} }
return $cmd; return $cmd;
...@@ -430,9 +431,11 @@ sub os_routing_add_manual($$$$$;$) ...@@ -430,9 +431,11 @@ sub os_routing_add_manual($$$$$;$)
$cmd = "$ROUTE add $rtabopt -host $destip $gate"; $cmd = "$ROUTE add $rtabopt -host $destip $gate";
} elsif ($routetype eq "net") { } elsif ($routetype eq "net") {
$cmd = "$ROUTE add $rtabopt -net $destip $gate $destmask"; $cmd = "$ROUTE add $rtabopt -net $destip $gate $destmask";
} elsif ($routetype eq "default") {
$cmd = "$ROUTE add $rtabopt default $gate";
} else { } else {
warn "*** WARNING: bad routing entry type: $routetype\n"; warn "*** WARNING: bad routing entry type: $routetype\n";
$cmd = ""; $cmd = "false";
} }
return $cmd; return $cmd;
...@@ -448,9 +451,11 @@ sub os_routing_del_manual($$$$$;$) ...@@ -448,9 +451,11 @@ sub os_routing_del_manual($$$$$;$)
$cmd = "$ROUTE delete $rtabopt -host $destip"; $cmd = "$ROUTE delete $rtabopt -host $destip";
} elsif ($routetype eq "net") { } elsif ($routetype eq "net") {
$cmd = "$ROUTE delete $rtabopt -net $destip $gate $destmask"; $cmd = "$ROUTE delete $rtabopt -net $destip $gate $destmask";
} elsif ($routetype eq "default") {
$cmd = "$ROUTE delete $rtabopt default";
} else { } else {
warn "*** WARNING: bad routing entry type: $routetype\n"; warn "*** WARNING: bad routing entry type: $routetype\n";
$cmd = ""; $cmd = "false";
} }
return $cmd; return $cmd;
...@@ -511,4 +516,57 @@ sub os_getnfsmounts($) ...@@ -511,4 +516,57 @@ sub os_getnfsmounts($)
return 0; return 0;
} }
sub os_fwconfig_line($@)
{
my ($fwinfo, @fwrules) = @_;
my ($upline, $downline);
$upline = "kldload ipfw.ko >/dev/null 2>&1\n";
foreach my $rule (sort { $a->{RULENO} <=> $b->{RULENO}} @fwrules) {
$upline .= " ipfw add $rule->{RULENO} $rule->{RULE} || {\n";
$upline .= " echo 'WARNING: could not load ipfw rule:'\n";
$upline .= " echo ' $rule->{RULE}'\n";
$upline .= " exit 1\n";
$upline .= " }\n";
}
$upline .= " sysctl net.inet.ip.redirect=0\n";
$upline .= " sysctl net.inet.ip.forwarding=1";
$downline = "sysctl net.inet.ip.forwarding=0\n";
$downline .= " sysctl net.inet.ip.redirect=1\n";
$downline .= " ipfw -q flush\n";
$downline .= " kldunload ipfw.ko >/dev/null 2>&1";
return ($upline, $downline);
}
sub os_fwrouteconfig_line($$$)
{
my ($orouter, $fwrouter, $routestr) = @_;
my ($upline, $downline);
#
# XXX assume the original default route should be used to reach servers.
#
# For setting up the firewall, this means we create explicit routes for
# each host via the original default route.
#
# For tearing down the firewall, we just remove the explicit routes
# and let them fall back on the now re-established original default route.
#
$upline = "for vir in $routestr; do\n";
$upline .= " $ROUTE -q delete \$vir >/dev/null 2>&1\n";
$upline .= " $ROUTE -q add \$vir $orouter || {\n";
$upline .= " echo \"Could not establish route for \$vir\"\n";
$upline .= " exit 1\n";
$upline .= " }\n";
$upline .= " done";
$downline = "for vir in $routestr; do\n";
$downline .= " $ROUTE -q delete \$vir >/dev/null 2>&1\n";
$downline .= " done";
return ($upline, $downline);
}
1; 1;
...@@ -19,6 +19,7 @@ use Exporter; ...@@ -19,6 +19,7 @@ use Exporter;
os_routing_enable_forward os_routing_enable_gated os_routing_enable_forward os_routing_enable_gated
os_routing_add_manual os_routing_del_manual os_homedirdel os_routing_add_manual os_routing_del_manual os_homedirdel
os_groupdel os_getnfsmounts os_groupdel os_getnfsmounts
os_fwconfig_line os_fwrouteconfig_line
); );
# Must come after package declaration! # Must come after package declaration!
...@@ -466,6 +467,8 @@ sub os_routing_add_manual($$$$$;$) ...@@ -466,6 +467,8 @@ sub os_routing_add_manual($$$$$;$)
$cmd = "$ROUTE add -host $destip gw $gate"; $cmd = "$ROUTE add -host $destip gw $gate";
} elsif ($routetype eq "net") { } elsif ($routetype eq "net") {
$cmd = "$ROUTE add -net $destip netmask $destmask gw $gate"; $cmd = "$ROUTE add -net $destip netmask $destmask gw $gate";
} elsif ($routetype eq "default") {
$cmd = "$ROUTE add default gw $gate";
} else { } else {
warn "*** WARNING: bad routing entry type: $routetype\n"; warn "*** WARNING: bad routing entry type: $routetype\n";
$cmd = ""; $cmd = "";
...@@ -483,6 +486,8 @@ sub os_routing_del_manual($$$$$;$) ...@@ -483,6 +486,8 @@ sub os_routing_del_manual($$$$$;$)
$cmd = "$ROUTE delete -host $destip"; $cmd = "$ROUTE delete -host $destip";