summaryrefslogtreecommitdiff
path: root/scripts/zone-mgmt/vyatta-zone.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/zone-mgmt/vyatta-zone.pl')
-rwxr-xr-xscripts/zone-mgmt/vyatta-zone.pl458
1 files changed, 458 insertions, 0 deletions
diff --git a/scripts/zone-mgmt/vyatta-zone.pl b/scripts/zone-mgmt/vyatta-zone.pl
new file mode 100755
index 00000000..8f081228
--- /dev/null
+++ b/scripts/zone-mgmt/vyatta-zone.pl
@@ -0,0 +1,458 @@
+#!/usr/bin/perl
+#
+# Module: vyatta-zone.pl
+#
+# **** License ****
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# This code was originally developed by Vyatta, Inc.
+# Portions created by Vyatta are Copyright (C) 2009 Vyatta, Inc.
+# All Rights Reserved.
+#
+# Author: Mohit Mehta
+# Date: April 2009
+# Description: Script for managing zones
+#
+# **** End License ****
+#
+
+use Getopt::Long;
+use POSIX;
+
+use lib "/opt/vyatta/share/perl5";
+use Vyatta::Config;
+use Vyatta::Misc;
+
+use warnings;
+use strict;
+
+# for future ease, when we add modify, these hashes will just be extended
+# firewall mapping from config node to iptables command.
+my %cmd_hash = ( 'name' => '/sbin/iptables',
+ 'ipv6-name' => '/sbin/ip6tables');
+
+# firewall mapping from config node to iptables/ip6tables table
+my %table_hash = ( 'name' => 'filter',
+ 'ipv6-name' => 'filter');
+
+my $debug="true";
+my $logger = 'sudo logger -t vyatta-zone.pl -p local0.warn --';
+
+sub run_cmd {
+ my $cmd = shift;
+
+ my $error = system("$cmd");
+ if ($debug eq "true") {
+ my $func = (caller(1))[3];
+ system("$logger [$func] [$cmd] = [$error]");
+ }
+ return $error;
+}
+
+sub get_all_zones {
+ my $value_func = shift;
+ my $config = new Vyatta::Config;
+ return $config->$value_func("zone-policy zone");
+}
+
+sub get_zone_interfaces {
+ my ($value_func, $zone_name) = @_;
+ my $config = new Vyatta::Config;
+ return $config->$value_func("zone-policy zone $zone_name interface");
+}
+
+sub get_from_zones {
+ my ($value_func, $zone_name) = @_;
+ my $config = new Vyatta::Config;
+ return $config->$value_func("zone-policy zone $zone_name from");
+}
+
+sub get_firewall_ruleset {
+ my ($value_func, $zone_name, $from_zone, $firewall_type) = @_;
+ my $config = new Vyatta::Config;
+ return $config->$value_func("zone-policy zone $zone_name from $from_zone
+ firewall $firewall_type");
+}
+
+sub is_local_zone {
+ my ($value_func, $zone_name) = @_;
+ my $config = new Vyatta::Config;
+ return $config->$value_func("zone-policy zone $zone_name local-zone");
+}
+
+sub rule_exists {
+ my ($tree, $chain_name, $target, $interface) = @_;
+ my $cmd =
+ "sudo $cmd_hash{$tree} -t $table_hash{$tree} -L " .
+ "$chain_name -v 2>/dev/null | grep \" $target \" " .
+ "| grep \" $interface \" | wc -l";
+ my $result = `$cmd`;
+ return $result;
+}
+
+sub create_zone_chain {
+ my $zone_name = shift;
+ my ($cmd, $error);
+ # create zone chains in filter, ip6filter tables
+ foreach my $tree (keys %cmd_hash) {
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -L zone-$zone_name >&/dev/null";
+ $error = run_cmd($cmd);
+ if ($error) {
+ print "$tree - $zone_name does not exists; create zone chain $zone_name\n";
+ # chain does not exist, go ahead create it
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -N zone-$zone_name";
+ $error = run_cmd($cmd);
+ return "Error: call to create $zone_name chain with failed [$error]" if $error;
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -I zone-$zone_name -j DROP";
+ $error = run_cmd($cmd);
+ return "Error: call to add drop rule to $zone_name chain with failed [$error]" if $error;
+ }
+ }
+ return;
+}
+
+sub delete_zone_chain {
+ my $zone_name = shift;
+ my ($cmd, $error);
+ # delete zone chains from filter, ip6filter tables
+ foreach my $tree (keys %cmd_hash) {
+ print "$tree - delete zone chain $zone_name\n";
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -F zone-$zone_name";
+ $error = run_cmd($cmd);
+ return "Error: call to flush all rules in $zone_name chain with failed [$error]" if $error;
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -X zone-$zone_name";
+ $error = run_cmd($cmd);
+ return "Error: call to delete $zone_name chain with failed [$error]" if $error;
+ }
+ return;
+}
+
+sub count_iptables_rules {
+ my ($type, $chain) = @_;
+ my @lines = `sudo $cmd_hash{$type} -t $table_hash{$type} -L $chain -n --line`;
+ my $cnt = 0;
+ foreach my $line (@lines) {
+ $cnt++ if $line =~ /^\d/;
+ }
+ return $cnt;
+}
+
+sub add_fromzone_intf_ruleset {
+ my ($zone_name, $from_zone, $interface, $ruleset_type, $ruleset) = @_;
+ # check if ruleset type has a value
+ my ($cmd, $error);
+ my $ruleset_name;
+ if (defined $ruleset) { # called from node.def
+ $ruleset_name=$ruleset;
+ } else { # called from do_firewall_interface_zone()
+ $ruleset_name=get_firewall_ruleset("returnValue", $zone_name, $from_zone, $ruleset_type);
+ }
+ if (defined $ruleset_name) {
+ print "$ruleset_type - insert rules for jumping to $ruleset_name for $interface in zone-$zone_name chain\n";
+ # get number of rules in ruleset_name
+ my $rule_cnt = count_iptables_rules($ruleset_type, "zone-$zone_name");
+ # append rules before last drop all rule
+ my $insert_at_rule_num=1;
+ if ( $rule_cnt > 1 ) {
+ $insert_at_rule_num=$rule_cnt;
+ }
+ my $result = rule_exists ($ruleset_type, "zone-$zone_name", $ruleset_name, $interface);
+ if ($result < 1) {
+ $cmd = "sudo $cmd_hash{$ruleset_type} -t $table_hash{$ruleset_type} " .
+ "-I zone-$zone_name $insert_at_rule_num -i $interface -j $ruleset_name";
+ $error = run_cmd($cmd);
+ return "Error: call to insert rule for incoming interface $interface
+into zone-chain zone-$zone_name with target $ruleset_name failed [$error]" if $error;
+ # insert the RETURN rule next
+ $insert_at_rule_num++;
+ $cmd = "sudo $cmd_hash{$ruleset_type} -t $table_hash{$ruleset_type} " .
+ "-I zone-$zone_name $insert_at_rule_num -i $interface -j RETURN";
+ $error = run_cmd($cmd);
+ return "Error: call to insert rule for incoming interface $interface
+into zone chain zone-$zone_name with target RETURN failed [$error]" if $error;
+ }
+ }
+ return;
+}
+
+sub delete_fromzone_intf_ruleset {
+ my ($zone_name, $from_zone, $interface, $ruleset_type, $ruleset) = @_;
+ # check if ruleset type has a value
+ my ($cmd, $error);
+ my $ruleset_name;
+ if (defined $ruleset) { # called from node.def
+ $ruleset_name=$ruleset;
+ } else { # called from undo_firewall_interface_zone()
+ $ruleset_name=get_firewall_ruleset("returnOrigValue", $zone_name, $from_zone, $ruleset_type);
+ }
+ if (defined $ruleset_name) {
+ print "$ruleset_type - delete rules for jumping to $ruleset_name for $interface in zone-$zone_name chain\n";
+ $cmd = "sudo $cmd_hash{$ruleset_type} -t $table_hash{$ruleset_type} " .
+ "-D zone-$zone_name -i $interface -j $ruleset_name";
+ $error = run_cmd($cmd);
+ return "Error: call to delete rule for incoming interface $interface
+in zone chain zone-$zone_name with target $ruleset_name failed [$error]" if $error;
+ $cmd = "sudo $cmd_hash{$ruleset_type} -t $table_hash{$ruleset_type} " .
+ "-D zone-$zone_name -i $interface -j RETURN";
+ $error = run_cmd($cmd);
+ return "Error: call to delete rule for incoming interface $interface into
+zone chain zone-$zone_name with target RETURN for $zone_name failed [$error]" if $error;
+ }
+ return;
+}
+
+sub do_firewall_interface_zone {
+ my ($zone_name, $interface) = @_;
+ my ($cmd, $error);
+ # add rule to allow same zone to same zone traffic
+ foreach my $tree (keys %cmd_hash) {
+ print "$tree - add interface $interface to zone $zone_name\n";
+ my $result = rule_exists ($tree, "zone-$zone_name", "RETURN", $interface);
+ if ($result < 1) {
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -I zone-$zone_name " .
+ "-i $interface -j RETURN";
+ $error = run_cmd($cmd);
+ return "Error: call to add $interface to its zone-chain zone-$zone_name
+failed [$error]" if $error;
+ }
+ # need to do this as an append before VYATTA_POST_FW_HOOK
+ my $rule_cnt = count_iptables_rules($tree, "FORWARD");
+ my $insert_at_rule_num=1;
+ if ( $rule_cnt > 1 ) {
+ $insert_at_rule_num=$rule_cnt;
+ }
+ $result = rule_exists ($tree, "FORWARD", "zone-$zone_name", $interface);
+ if ($result < 1) {
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -I FORWARD $insert_at_rule_num " .
+ "-o $interface -j zone-$zone_name";
+ $error = run_cmd($cmd);
+ return "Error: call to add jump rule for outgoing interface $interface to
+its zone-$zone_name chain failed [$error]" if $error;
+ }
+ }
+
+ # get all zones in which this zone is being used as a from zone
+ # then in chains for those zones, add rules for this incoming interface
+ my @all_zones = get_all_zones("listNodes");
+ foreach my $zone (@all_zones) {
+ if (!($zone eq $zone_name)) {
+ my @from_zones = get_from_zones("listNodes", $zone);
+ if (scalar(grep(/^$zone_name$/, @from_zones)) > 0) {
+ foreach my $tree (keys %cmd_hash) {
+ # call function to append rules to $zone's chain
+ $error = add_fromzone_intf_ruleset($zone, $zone_name,
+ $interface, $tree);
+ return "Error: $error" if $error;
+ }
+ }
+ }
+ }
+ return;
+}
+
+sub undo_firewall_interface_zone {
+ my ($zone_name, $interface) = @_;
+ my ($cmd, $error);
+
+ # delete rule to allow same zone to same zone traffic
+ foreach my $tree (keys %cmd_hash) {
+ print "$tree - delete interface $interface from zone $zone_name\n";
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -D FORWARD " .
+ "-o $interface -j zone-$zone_name";
+ $error = run_cmd($cmd);
+ return "Error: call to delete jump rule for outgoing interface $interface
+to zone-$zone_name chain failed [$error]" if $error;
+
+ $cmd = "sudo $cmd_hash{$tree} -t $table_hash{$tree} -D zone-$zone_name " .
+ "-i $interface -j RETURN";
+ $error = run_cmd($cmd);
+ return "Error: call to delete interface $interface from zone-chain
+zone-$zone_name with failed [$error]" if $error;
+ }
+
+ # delete rules for this interface where this zone is being used as a from zone
+ my @all_zones = get_all_zones("listOrigNodes");
+ foreach my $zone (@all_zones) {
+ if (!($zone eq $zone_name)) {
+ my @from_zones = get_from_zones("listOrigNodes", $zone);
+ if (scalar(grep(/^$zone_name$/, @from_zones)) > 0) {
+ foreach my $tree (keys %cmd_hash) {
+ # call function to delete rules from $zone's chain
+ $error = delete_fromzone_intf_ruleset($zone, $zone_name,
+ $interface, $tree);
+ return "Error: $error" if $error;
+ }
+ }
+ }
+ }
+ return;
+}
+
+sub add_zone {
+ my $zone_name = shift;
+ print "perform add zone actions for $zone_name\n";
+ # perform firewall related actions for this zone
+ my $error = create_zone_chain ($zone_name);
+ return ($error, ) if $error;
+ return;
+}
+
+sub delete_zone {
+ my $zone_name = shift;
+ print "perform delete zone actions for $zone_name\n";
+ # undo firewall related actions for this zone
+ my $error = delete_zone_chain ($zone_name);
+ return ($error, ) if $error;
+ return;
+}
+
+sub add_zone_interface {
+ my ($zone_name, $interface) = @_;
+ print "perform add interface $interface to zone $zone_name\n";
+ return("Error: undefined interface", ) if ! defined $interface;
+ my $error;
+ # do firewall related stuff
+ $error = do_firewall_interface_zone ($zone_name, $interface);
+ return ($error, ) if $error;
+ return;
+}
+
+sub delete_zone_interface {
+ my ($zone_name, $interface) = @_;
+ print "perform delete interface $interface from zone $zone_name\n";
+ return("Error: undefined interface", ) if ! defined $interface;
+ # undo firewall related stuff
+ my $error = undo_firewall_interface_zone ($zone_name, $interface);
+ return ($error, ) if $error;
+ return;
+}
+
+sub add_fromzone_fw {
+ my ($zone, $from_zone, $ruleset_type, $ruleset_name) = @_;
+ my $error;
+ # get all interfaces in from_zone
+ # call sub add_fromzone_intf_ruleset for each interface in from zone with these parameters
+ # $zone_name, $from_zone, $interface, $ruleset_type
+ print "apply $ruleset_type ruleset to filter traffic from zone $from_zone to $zone\n";
+ my @from_zone_interfaces = get_zone_interfaces("returnValues", $from_zone);
+ foreach my $intf (@from_zone_interfaces) {
+ $error = add_fromzone_intf_ruleset($zone, $from_zone, $intf, $ruleset_type, $ruleset_name);
+ return "Error: $error" if $error;
+ }
+ return;
+}
+
+sub delete_fromzone_fw {
+ my ($zone, $from_zone, $ruleset_type, $ruleset_name) = @_;
+ my $error;
+ # get all interfaces in from_zone
+ # call sub delete_fromzone_intf_ruleset for each interface in from zone with these parameters
+ # $zone_name, $from_zone, $interface, $ruleset_type
+ print "delete $ruleset_type ruleset to filter traffic from zone $from_zone to $zone\n";
+ my @from_zone_interfaces = get_zone_interfaces("returnOrigValues", $from_zone);
+ foreach my $intf (@from_zone_interfaces) {
+ $error = delete_fromzone_intf_ruleset($zone, $from_zone, $intf, $ruleset_type, $ruleset_name);
+ return "Error: $error" if $error;
+ }
+ return;
+}
+
+sub validity_checks {
+ my @all_zones = get_all_zones("listNodes");
+ my @all_interfaces = ();
+ my $num_local_zones = 0;
+ foreach my $zone (@all_zones) {
+ # get all from zones, see if they exist in config, if not => error out
+ print "check all from zones under $zone have zone definitions for them\n";
+ my @from_zones = get_from_zones("listNodes", $zone);
+ foreach my $from_zone (@from_zones) {
+ if (scalar(grep(/^$from_zone$/, @all_zones)) == 0) {
+ return ("from zone $from_zone under zone $zone is either not defined or deleted from config", );
+ }
+ }
+ print "check $zone has either interfaces defined or is local-zone\n";
+ my @zone_intfs = get_zone_interfaces("returnValues", $zone);
+ if (scalar(@zone_intfs) == 0) {
+ # no interfaces defined for this zone
+ if (!defined(is_local_zone("exists", $zone))) {
+ return("Zone $zone has no interfaces defined and it's not a local-zone", );
+ }
+ $num_local_zones++;
+ # make sure only one zone is a local-zone
+ if ($num_local_zones > 1) {
+ return ("Only one zone can be defined as a local-zone", );
+ }
+ } else {
+ # zone has interfaces defined for it, make sure it is not set as a local-zone
+ if (defined(is_local_zone("exists", $zone))) {
+ return("Zone $zone has interfaces defined. It cannot be a local-zone", );
+ }
+ # check for each interface if it is in @all_interfaces, if not push it to @all_interfaces
+ foreach my $interface (@zone_intfs) {
+ if (scalar(grep(/^$interface$/, @all_interfaces)) > 0) {
+ return ("interface $interface defined under two zones. @all_interfaces", );
+ } else {
+ push(@all_interfaces, $interface);
+ }
+ }
+ }
+ }
+ return;
+}
+
+#
+# main
+#
+
+my ($action, $zone_name, $interface, $from_zone, $ruleset_type, $ruleset_name);
+
+GetOptions("action=s" => \$action,
+ "zone-name=s" => \$zone_name,
+ "interface=s" => \$interface,
+ "from-zone=s" => \$from_zone,
+ "ruleset-type=s" => \$ruleset_type,
+ "ruleset-name=s" => \$ruleset_name,
+);
+
+die "undefined action" if ! defined $action;
+die "undefined zone" if ! defined $zone_name;
+
+my ($error, $warning);
+
+($error, $warning) = add_zone($zone_name) if $action eq 'add-zone';
+
+($error, $warning) = delete_zone($zone_name) if $action eq 'delete-zone';
+
+($error, $warning) = add_zone_interface($zone_name, $interface)
+ if $action eq 'add-zone-interface';
+
+($error, $warning) = delete_zone_interface($zone_name, $interface)
+ if $action eq 'delete-zone-interface';
+
+($error, $warning) = add_fromzone_fw($zone_name, $from_zone, $ruleset_type, $ruleset_name)
+ if $action eq 'add-fromzone-fw';
+
+($error, $warning) = delete_fromzone_fw($zone_name, $from_zone, $ruleset_type, $ruleset_name)
+ if $action eq 'delete-fromzone-fw';
+
+($error, $warning) = validity_checks() if $action eq 'validity-checks';
+
+if (defined $warning) {
+ print "$warning\n";
+}
+
+if (defined $error) {
+ print "$error\n";
+ exit 1;
+}
+
+exit 0;
+
+# end of file