# # (c) Jan Gehring # # vim: set ts=3 sw=3 tw=0: # vim: set expandtab: =head1 NAME Rex::Commands::Iptables - Iptable Management Commands =head1 DESCRIPTION With this Module you can manage basic Iptables rules. =head1 SYNOPSIS use Rex::Commands::Iptables; task "firewall", sub { iptables_clear; open_port 22; open_port [22, 80] => { dev => "eth0", }; close_port 22 => { dev => "eth0", }; close_port "all"; redirect_port 80 => 10080; redirect_port 80 => { dev => "eth0", to => 10080, }; default_state_rule; default_state_rule dev => "eth0"; is_nat_gateway; iptables t => "nat", A => "POSTROUTING", o => "eth0", j => "MASQUERADE"; }; =head1 EXPORTED FUNCTIONS =over 4 =cut package Rex::Commands::Iptables; use strict; use warnings; require Rex::Exporter; use Data::Dumper; use base qw(Rex::Exporter); use vars qw(@EXPORT); use Rex::Commands::Sysctl; use Rex::Commands::Run; use Rex::Commands::Gather; use Rex::Logger; @EXPORT = qw(iptables is_nat_gateway iptables_list iptables_clear open_port close_port redirect_port default_state_rule); sub iptables; =item open_port($port, $option) Open a port for inbound connections. task "firewall", sub { open_port 22; open_port [22, 80]; open_port [22, 80] => { dev => "eth1", }; }; =cut sub open_port { my ($port, $option) = @_; _open_or_close_port("i", "I", "INPUT", "ACCEPT", $port, $option); } =item close_port($port, $option) Close a port for inbound connections. task "firewall", sub { close_port 22; close_port [22, 80]; close_port [22, 80] => { dev => "eth0", }; }; =cut sub close_port { my ($port, $option) = @_; _open_or_close_port("i", "A", "INPUT", "DROP", $port, $option); } =item redirect_port($in_port, $option) Redirect $in_port to an other local port. task "redirects", sub { redirect_port 80 => 10080; redirect_port 80 => { to => 10080, dev => "eth0", }; }; =cut sub redirect_port { my ($in_port, $option) = @_; my @opts; push (@opts, "t", "nat"); if(! ref($option)) { my $net_info = network_interfaces(); my @devs = keys %{$net_info}; for my $dev (@devs) { redirect_port($in_port, { dev => $dev, to => $option, }); } return; } unless(exists $option->{"dev"}) { my $net_info = network_interfaces(); my @devs = keys %{$net_info}; for my $dev (@devs) { $option->{"dev"} = $dev; redirect_port($in_port, $option); } return; } if($option->{"to"} =~ m/^\d+$/) { $option->{"proto"} ||= "tcp"; push(@opts, "I", "PREROUTING", "i", $option->{"dev"}, "p", $option->{"proto"}, "m", $option->{"proto"}); push(@opts, "dport", $in_port, "j", "REDIRECT", "to-ports", $option->{"to"}); } else { Rex::Logger::info("Redirect to other hosts isn't supported right now. Please do it by hand."); } iptables @opts; } =item iptables(@params) Write standard iptable comands. task "firewall", sub { iptables t => "nat", A => "POSTROUTING", o => "eth0", j => "MASQUERADE"; iptables t => "filter", i => "eth0", m => "state", state => "RELATED,ESTABLISHED", j => "ACCEPT"; iptables "flush"; iptables -F; iptables flush => "filter"; iptables -F => "filter"; }; =cut sub iptables { my (@params) = @_; if($params[0] eq "flush" || $params[0] eq "-flush" || $params[0] eq "-F") { if($params[1]) { run "iptables -F -t $params[1]"; } else { run "iptables -F"; } return; } my $cmd = ""; my $n = -1; while( $params[++$n] ) { my ($key, $val) = reverse @params[$n, $n++]; if(ref($key) eq "ARRAY") { $cmd .= join(" ", @{$key}); last; } if(length($key) == 1) { $cmd .= "-$key $val "; } else { $cmd .= "--$key $val "; } } if(can_run("iptables")) { run "iptables $cmd"; if($? != 0) { Rex::Logger::info("Error setting iptable rule: $cmd", "warn"); die("Error setting iptable rule: $cmd"); } } else { Rex::Logger::info("IPTables not found."); die("IPTables not found."); } } =item is_nat_gateway This function create a nat gateway for the device the default route points to. task "make-gateway", sub { is_nat_gateway; }; =cut sub is_nat_gateway { Rex::Logger::debug("Changing this system to a nat gateway."); if(can_run("ip")) { my @iptables_option = (); my ($default_line) = run "/sbin/ip r |grep ^default"; my ($dev) = ($default_line =~ m/dev ([a-z0-9]+)/i); Rex::Logger::debug("Default GW Device is $dev"); sysctl "net.ipv4.ip_forward" => 1; iptables t => "nat", A => "POSTROUTING", o => $dev, j => "MASQUERADE"; } else { Rex::Logger::info("No /sbin/ip found."); } } =item default_state_rule(%option) Set the default state rules for the given device. task "firewall", sub { default_state_rule(dev => "eth0"); }; =cut sub default_state_rule { my (%option) = @_; unless(exists $option{"dev"}) { my $net_info = network_interfaces(); my @devs = keys %{$net_info}; for my $dev (@devs) { default_state_rule(dev => $dev); } return; } iptables t => "filter", A => "INPUT", i => $option{"dev"}, m => "state", state => "RELATED,ESTABLISHED", j => "ACCEPT"; } =item iptables_list List all iptables rules. task "list-iptables", sub { print Dumper iptables_list; }; =cut sub iptables_list { my @lines = run "/sbin/iptables-save"; _iptables_list(@lines); } sub _iptables_list { my (%tables, $ret); my (@lines) = @_; my ($current_table); for my $line (@lines) { chomp $line; next if($line eq "COMMIT"); next if($line =~ m/^#/); next if($line =~ m/^:/); if($line =~ m/^\*([a-z]+)$/) { $current_table = $1; $tables{$current_table} = []; next; } #my @parts = grep { ! /^\s+$/ && ! /^$/ } split (/(\-\-?[^\s]+\s[^\s]+)/i, $line); my @parts = grep { ! /^\s+$/ && ! /^$/ } split (/^\-\-?|\s+\-\-?/i, $line); my @option = (); for my $part (@parts) { my ($key, $value) = split(/\s/, $part, 2); #$key =~ s/^\-+//; push(@option, $key => $value); } push (@{$ret->{$current_table}}, \@option); } return $ret; } =item iptables_clear Remove all iptables rules. task "no-firewall", sub { iptables_clear; }; =cut sub iptables_clear { for my $table (qw/nat mangle filter/) { iptables t => $table, F => ''; iptables t => $table, X => ''; } for my $p (qw/INPUT FORWARD OUTPUT/) { iptables P => $p, ["ACCEPT"]; } } sub _open_or_close_port { my ($dev_type, $push_type, $chain, $jump, $port, $option) = @_; my @opts; push(@opts, "t", "filter", "$push_type", "$chain"); unless(exists $option->{"dev"}) { my $net_info = network_interfaces(); my @dev = keys %{$net_info}; $option->{"dev"} = \@dev; } if(exists $option->{"dev"} && ! ref($option->{"dev"})) { push(@opts, "$dev_type", $option->{"dev"}); } elsif(ref($option->{"dev"}) eq "ARRAY") { for my $dev (@{$option->{"dev"}}) { my $new_option = $option; $new_option->{"dev"} = $dev; _open_or_close_port($dev_type, $push_type, $chain, $jump, $port, $new_option); } return; } if(exists $option->{"proto"}) { push(@opts, "p", $option->{"proto"}); push(@opts, "m", $option->{"proto"}); } else { push(@opts, "p", "tcp"); push(@opts, "m", "tcp"); } if($port eq "all") { push(@opts, "j", "$jump"); } else { if(ref($port) eq "ARRAY") { for my $port_num (@{$port}) { _open_or_close_port($dev_type, $push_type, $chain, $jump, $port_num, $option); } return; } push(@opts, "j", $jump); push(@opts, "dport", $port); } iptables @opts; } =back =cut 1;