]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/pve-firewall
bump version to 1.0-13
[pve-firewall.git] / src / pve-firewall
index 7de7355e8140244d6a2ad1f33c0492307cae0f05..e7a2337c4c9da59c19c4eda999b369303ef62a8c 100755 (executable)
@@ -13,6 +13,8 @@ use PVE::Cluster qw(cfs_read_file);
 use PVE::RPCEnvironment;
 use PVE::CLIHandler;
 use PVE::Firewall;
+use PVE::FirewallSimulator;
+use Data::Dumper;
 
 use base qw(PVE::CLIHandler);
 
@@ -41,6 +43,8 @@ $rpcenv->init_request();
 $rpcenv->set_language($ENV{LANG});
 $rpcenv->set_user('root@pam');
 
+my $nodename = PVE::INotify::nodename();
+
 my $commandline = [$0, @ARGV];
 
 $0 = "pve-firewall";
@@ -54,6 +58,8 @@ sub restart_server {
 
     sleep($waittime) if $waittime; # avoid high server load due to restarts
 
+    PVE::INotify::inotify_close();
+
     exec (@$commandline);
     exit (-1); # never reached?
 }
@@ -114,6 +120,10 @@ sub run_server {
 
     delete $ENV{RESTART_PVE_FIREWALL};
 
+    PVE::Cluster::cfs_update();
+    
+    PVE::Firewall::init();
+
     if (!$param->{debug}) {
        open STDIN,  '</dev/null' || die "can't read /dev/null";
        open STDOUT, '>/dev/null' || die "can't write /dev/null";
@@ -251,7 +261,7 @@ __PACKAGE__->register_method ({
     name => 'stop',
     path => 'stop',
     method => 'POST',
-    description => "Stop firewall. This will remove all rules installed by this script. The host is unprotected afterwards.",
+    description => "Stop firewall. This removes all Proxmox VE related iptable rules. The host is unprotected afterwards.",
     parameters => {
        additionalProperties => 0,
        properties => {},
@@ -301,7 +311,11 @@ __PACKAGE__->register_method ({
        properties => {
            status => {
                type => 'string',
-               enum => ['unknown', 'stopped', 'active'],
+               enum => ['unknown', 'stopped', 'running'],
+           },
+           enable => {
+               description => "Firewall is enabled (in 'cluster.fw')",
+               type => 'boolean',
            },
            changes => {
                description => "Set when there are pending changes.",
@@ -320,16 +334,24 @@ __PACKAGE__->register_method ({
            my $pid = int(PVE::Tools::file_read_firstline($pve_firewall_pidfile) || 0);
            my $running = PVE::ProcFSTools::check_process_running($pid);
 
-           my $status = $running ? 'active' : 'stopped';
+           my $status = $running ? 'running' : 'stopped';
 
            my $res = { status => $status };
-           if ($status eq 'active') {
-               my ($ruleset, $ipset_ruleset) = PVE::Firewall::compile();
 
-               my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset);
-               my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset);
-             
-               $res->{changes} = ($ipset_changes || $ruleset_changes) ? 1 : 0;
+           my $verbose = 1; # show syntax errors 
+           my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); 
+           $res->{enable} = $cluster_conf->{options}->{enable} ? 1 : 0;
+
+           if ($status eq 'running') {
+               
+               my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
+
+               $verbose = 0; # do not show iptables details
+               my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose);
+               my ($test, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, $verbose);
+               my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables");
+
+               $res->{changes} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6) ? 1 : 0;
            }
 
            return $res;
@@ -341,8 +363,8 @@ __PACKAGE__->register_method ({
 __PACKAGE__->register_method ({
     name => 'compile',
     path => 'compile',
-    method => 'POST',
-    description => "Compile amd print firewall rules. This is only for testing.",
+    method => 'GET',
+    description => "Compile and print firewall rules. This is useful for testing.",
     parameters => {
        additionalProperties => 0,
        properties => {},
@@ -355,15 +377,30 @@ __PACKAGE__->register_method ({
        local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
 
        my $code = sub {
-           my ($ruleset, $ipset_ruleset) = PVE::Firewall::compile();
 
-           my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, 1);
-           my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, 1);
-           if ($ipset_changes || $ruleset_changes) {
+           my $verbose = 1;
+
+           my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); 
+           my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile($cluster_conf, undef, undef, $verbose);
+
+           print "ipset cmdlist:\n";
+           my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose);
+
+           print "\niptables cmdlist:\n";
+           my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, $verbose);
+
+           print "\nip6tables cmdlist:\n";
+           my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables");
+
+           if ($ipset_changes || $ruleset_changes || $ruleset_changesv6) {
                print "detected changes\n";
            } else {
                print "no changes\n";
            }
+           if (!$cluster_conf->{options}->{enable}) {
+               print "firewall disabled\n";
+           }
+
        };
 
        PVE::Firewall::run_locked($code);
@@ -371,18 +408,167 @@ __PACKAGE__->register_method ({
        return undef;
     }});
 
-my $nodename = PVE::INotify::nodename();
+__PACKAGE__->register_method ({
+    name => 'localnet',
+    path => 'localnet',
+    method => 'GET',
+    description => "Print information about local network.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {},
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
+
+       my $nodename = PVE::INotify::nodename();
+       print "local hostname: $nodename\n";
+
+       my $ip = PVE::Cluster::remote_node_ip($nodename);
+       print "local IP address: $ip\n";
+
+       my $cluster_conf = PVE::Firewall::load_clusterfw_conf();
+       
+       my $localnet = PVE::Firewall::local_network() || '127.0.0.0/8';
+       print "network auto detect: $localnet\n";
+       if ($cluster_conf->{aliases}->{local_network}) {
+           print "using user defined local_network: $cluster_conf->{aliases}->{local_network}->{cidr}\n";
+       } else {
+           print "using detected local_network: $localnet\n";
+       }
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'simulate',
+    path => 'simulate',
+    method => 'GET',
+    description => "Simulate firewall rules. This does not simulate kernel 'routing' table. Instead, this simply assumes that routing from source zone to destination zone is possible.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           verbose => {
+               description => "Verbose output.",
+               type => 'boolean',
+               optional => 1,
+               default => 0,
+           },
+           from => {
+               description => "Source zone.",
+               type => 'string',
+               pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
+               optional => 1,
+               default => 'outside',
+           },
+           to => {
+               description => "Destination zone.",
+               type => 'string',
+               pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)',
+               optional => 1,
+               default => 'host',
+           },
+           protocol => {
+               description => "Protocol.",
+               type => 'string',
+               pattern => '(tcp|udp)',
+               optional => 1,
+               default => 'tcp',
+           },
+           dport => {
+               description => "Destination port.",
+               type => 'integer',
+               minValue => 1,
+               maxValue => 65535,
+               optional => 1,
+           },
+           sport => {
+               description => "Source port.",
+               type => 'integer',
+               minValue => 1,
+               maxValue => 65535,
+               optional => 1,
+           },
+           source => {
+               description => "Source IP address.",
+               type => 'string', format => 'ipv4',
+               optional => 1,
+           },
+           dest => {
+               description => "Destination IP address.",
+               type => 'string', format => 'ipv4',
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'null' },
+    code => sub {
+       my ($param) = @_;
+
+       local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog
+
+       my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose});
+
+       PVE::FirewallSimulator::debug($param->{verbose} || 0);
+       
+       my $host_ip = PVE::Cluster::remote_node_ip($nodename);
+
+       PVE::FirewallSimulator::reset_trace();
+       print Dumper($ruleset) if $param->{verbose};
+
+       my $test = {
+           from => $param->{from},
+           to => $param->{to},
+           proto => $param->{protocol} || 'tcp',
+           source => $param->{source},
+           dest => $param->{dest},
+           dport => $param->{dport},
+           sport => $param->{sport},
+       };
+
+       if (!defined($test->{to})) {
+           $test->{to} = 'host';
+           PVE::FirewallSimulator::add_trace("Set Zone: to => '$test->{to}'\n"); 
+       } 
+       if (!defined($test->{from})) {
+           $test->{from} = 'outside',
+           PVE::FirewallSimulator::add_trace("Set Zone: from => '$test->{from}'\n"); 
+       }
+
+       my $vmdata = PVE::Firewall::read_local_vm_config();
+
+       print "Test packet:\n";
+
+       foreach my $k (qw(from to proto source dest dport sport)) {
+           printf("  %-8s: %s\n", $k, $test->{$k}) if defined($test->{$k});
+       }
+
+       $test->{action} = 'QUERY';
+
+       my $res = PVE::FirewallSimulator::simulate_firewall($ruleset, $ipset_ruleset, 
+                                                           $host_ip, $vmdata, $test);
+       
+       print "ACTION: $res\n";
+
+       return undef;
+    }});
 
 my $cmddef = {
     start => [ __PACKAGE__, 'start', []],
     stop => [ __PACKAGE__, 'stop', []],
     compile => [ __PACKAGE__, 'compile', []],
+    simulate => [ __PACKAGE__, 'simulate', []],
+    localnet => [ __PACKAGE__, 'localnet', []],
     status => [ __PACKAGE__, 'status', [], undef, sub {
        my $res = shift;
+       my $status = ($res->{enable} ? "enabled" : "disabled") . '/' . $res->{status};
+       
        if ($res->{changes}) {
-           print "Status: $res->{status} (pending changes)\n";
+           print "Status: $status (pending changes)\n";
        } else {
-           print "Status: $res->{status}\n";
+           print "Status: $status\n";
        }
     }],
  };
@@ -397,16 +583,14 @@ __END__
 
 =head1 NAME
                                           
-pvestatd - PVE Firewall Daemon
+pve-firewall - PVE Firewall Daemon
 
 =head1 SYNOPSIS
 
-pve-firewall
+=include synopsis
 
 =head1 DESCRIPTION
 
 This service updates iptables rules periodically.
 
-
-
-
+=include pve_copyright