X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FService%2Fpve_firewall.pm;h=5a62f3dbf1710cee1f4ad90715cd3c2fa56c612e;hb=HEAD;hp=9f712e0d16a856b4c4b3c1ff2e0e801b6db7a8ad;hpb=0c32b7fbac4fa47a15724db1a3d9f7602ac14569;p=pve-firewall.git diff --git a/src/PVE/Service/pve_firewall.pm b/src/PVE/Service/pve_firewall.pm index 9f712e0..65cb2b8 100755 --- a/src/PVE/Service/pve_firewall.pm +++ b/src/PVE/Service/pve_firewall.pm @@ -2,19 +2,23 @@ package PVE::Service::pve_firewall; use strict; use warnings; -use PVE::SafeSyslog; -use PVE::Daemon; -use Time::HiRes qw (gettimeofday); -use PVE::Tools qw(dir_glob_foreach file_read_firstline); -use PVE::ProcFSTools; -use PVE::INotify; +use Data::Dumper; +use Time::HiRes qw (gettimeofday usleep); + +use PVE::CLIHandler; use PVE::Cluster qw(cfs_read_file); +use PVE::Corosync; +use PVE::Daemon; +use PVE::INotify; +use PVE::ProcFSTools; use PVE::RPCEnvironment; -use PVE::CLIHandler; +use PVE::SafeSyslog; +use PVE::Tools qw(dir_glob_foreach file_read_firstline); + use PVE::Firewall; use PVE::FirewallSimulator; -use Data::Dumper; +use PVE::FirewallSimulator qw($bridge_interface_pattern); use base qw(PVE::Daemon); @@ -27,16 +31,12 @@ my $daemon = __PACKAGE__->new('pve-firewall', $cmdline, %daemon_options); my $nodename = PVE::INotify::nodename(); sub init { - PVE::Cluster::cfs_update(); - + PVE::Firewall::init(); } -my $restart_request = 0; -my $next_update = 0; - -my $cycle = 0; +my ($next_update, $cycle, $restart_request) = (0, 0, 0); my $updatetime = 10; my $initial_memory_usage; @@ -44,12 +44,12 @@ my $initial_memory_usage; sub shutdown { my ($self) = @_; - syslog('info' , "server closing"); + syslog('info' , "server shutting down"); # wait for children 1 while (waitpid(-1, POSIX::WNOHANG()) > 0); - - syslog('info' , "clear firewall rules"); + + syslog('info' , "clear PVE-generated firewall rules"); eval { PVE::Firewall::remove_pvefw_chains(); }; warn $@ if $@; @@ -69,7 +69,6 @@ sub run { local $SIG{'__WARN__'} = 'IGNORE'; # do not fill up logs for (;;) { # forever - $next_update = time() + $updatetime; my ($ccsec, $cusec) = gettimeofday (); @@ -77,13 +76,11 @@ sub run { PVE::Cluster::cfs_update(); PVE::Firewall::update(); }; - my $err = $@; - - if ($err) { + if (my $err = $@) { syslog('err', "status update error: $err"); } - my ($ccsec_end, $cusec_end) = gettimeofday (); + my ($ccsec_end, $cusec_end) = gettimeofday(); my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000; syslog('info', sprintf("firewall update time (%.3f seconds)", $cptime)) @@ -92,12 +89,12 @@ sub run { $cycle++; my $mem = PVE::ProcFSTools::read_memory_usage(); - + if (!defined($initial_memory_usage) || ($cycle < 10)) { $initial_memory_usage = $mem->{resident}; } else { my $diff = $mem->{resident} - $initial_memory_usage; - if ($diff > 5*1024*1024) { + if ($diff > 5 * 1024 * 1024) { syslog ('info', "restarting server after $cycle cycles to " . "reduce memory usage (free $mem->{resident} ($diff) bytes)"); $self->restart_daemon(); @@ -105,19 +102,20 @@ sub run { } my $wcount = 0; - while ((time() < $next_update) && + while ((time() < $next_update) && ($wcount < $updatetime) && # protect against time wrap !$restart_request) { $wcount++; sleep (1); }; - + $self->restart_daemon() if $restart_request; } } $daemon->register_start_command("Start the Proxmox VE firewall service."); $daemon->register_restart_command(1, "Restart the Proxmox VE firewall service."); -$daemon->register_stop_command("Stop firewall. This removes all Proxmox VE " . - "related iptable rules. " . - "The host is unprotected afterwards."); +$daemon->register_stop_command( + "Stop the Proxmox VE firewall service. Note, stopping actively removes all Proxmox VE related" + ." iptable rules rendering the host potentially unprotected." +); __PACKAGE__->register_method ({ name => 'status', @@ -125,10 +123,10 @@ __PACKAGE__->register_method ({ method => 'GET', description => "Get firewall status.", parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => {}, }, - returns => { + returns => { type => 'object', additionalProperties => 0, properties => { @@ -158,20 +156,24 @@ __PACKAGE__->register_method ({ my $res = { status => $status }; - my $verbose = 1; # show syntax errors - my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef, $verbose); + PVE::Firewall::set_verbose(1); # show syntax errors + + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef); $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"); + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef); - $res->{changes} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6) ? 1 : 0; + PVE::Firewall::set_verbose(0); # do not show iptables details + my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset); + my ($test, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset->{filter}); + my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6->{filter}, "ip6tables"); + my (undef, $ruleset_changes_raw) = PVE::Firewall::get_ruleset_cmdlist($ruleset->{raw}, undef, 'raw'); + my (undef, $ruleset_changesv6_raw) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6->{raw}, "ip6tables", 'raw'); + my (undef, $ebtables_changes) = PVE::Firewall::get_ebtables_cmdlist($ebtables_ruleset); + + $res->{changes} = ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes || $ruleset_changes_raw || $ruleset_changesv6_raw) ? 1 : 0; } return $res; @@ -186,7 +188,7 @@ __PACKAGE__->register_method ({ method => 'GET', description => "Compile and print firewall rules. This is useful for testing.", parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => {}, }, returns => { type => 'null' }, @@ -198,21 +200,31 @@ __PACKAGE__->register_method ({ my $code = sub { - my $verbose = 1; + PVE::Firewall::set_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); + my $cluster_conf = PVE::Firewall::load_clusterfw_conf(undef); + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile($cluster_conf, undef, undef); print "ipset cmdlist:\n"; - my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset, $verbose); + my (undef, undef, $ipset_changes) = PVE::Firewall::get_ipset_cmdlist($ipset_ruleset); print "\niptables cmdlist:\n"; - my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset, $verbose); + my (undef, $ruleset_changes) = PVE::Firewall::get_ruleset_cmdlist($ruleset->{filter}); print "\nip6tables cmdlist:\n"; - my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6, $verbose, "ip6tables"); + my (undef, $ruleset_changesv6) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6->{filter}, "ip6tables"); + + print "\nebtables cmdlist:\n"; + my (undef, $ebtables_changes) = PVE::Firewall::get_ebtables_cmdlist($ebtables_ruleset); - if ($ipset_changes || $ruleset_changes || $ruleset_changesv6) { + print "\niptables table raw cmdlist:\n"; + my (undef, $ruleset_changes_raw) = PVE::Firewall::get_ruleset_cmdlist($ruleset->{raw}, undef, 'raw'); + + print "\nip6tables table raw cmdlist:\n"; + my (undef, $ruleset_changesv6_raw) = PVE::Firewall::get_ruleset_cmdlist($rulesetv6->{raw}, "ip6tables", 'raw'); + + + if ($ipset_changes || $ruleset_changes || $ruleset_changesv6 || $ebtables_changes || $ruleset_changes_raw || $ruleset_changesv6_raw) { print "detected changes\n"; } else { print "no changes\n"; @@ -220,7 +232,6 @@ __PACKAGE__->register_method ({ if (!$cluster_conf->{options}->{enable}) { print "firewall disabled\n"; } - }; PVE::Firewall::run_locked($code); @@ -234,7 +245,7 @@ __PACKAGE__->register_method ({ method => 'GET', description => "Print information about local network.", parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => {}, }, returns => { type => 'null' }, @@ -250,15 +261,37 @@ __PACKAGE__->register_method ({ 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"; + if (my $local_network = $cluster_conf->{aliases}->{local_network}) { + print "using user defined local_network: $local_network->{cidr}\n"; } else { print "using detected local_network: $localnet\n"; } + if (PVE::Corosync::check_conf_exists(1)) { + my $corosync_conf = PVE::Cluster::cfs_read_file("corosync.conf"); + my $corosync_node_found = 0; + + print "\naccepting corosync traffic from/to:\n"; + + PVE::Corosync::for_all_corosync_addresses($corosync_conf, undef, sub { + my ($curr_node_name, $curr_node_ip, undef, $key) = @_; + + return if $curr_node_name eq $nodename; + + $corosync_node_found = 1; + + $key =~ m/(?:ring|link)(\d+)_addr/; + print " - $curr_node_name: $curr_node_ip (link: $1)\n"; + }); + + if (!$corosync_node_found) { + print " - no nodes found\n"; + } + } + return undef; }}); @@ -266,9 +299,10 @@ __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.", + description => "Simulate firewall rules. This does not simulates the kernel 'routing' table," + ." but simply assumes that routing from source zone to destination zone is possible.", parameters => { - additionalProperties => 0, + additionalProperties => 0, properties => { verbose => { description => "Verbose output.", @@ -279,14 +313,14 @@ __PACKAGE__->register_method ({ from => { description => "Source zone.", type => 'string', - pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)', + pattern => "(host|outside|vm\\d+|ct\\d+|$bridge_interface_pattern)", optional => 1, default => 'outside', }, to => { description => "Destination zone.", type => 'string', - pattern => '(host|outside|vm\d+|ct\d+|vmbr\d+/\S+)', + pattern => "(host|outside|vm\\d+|ct\\d+|$bridge_interface_pattern)", optional => 1, default => 'host', }, @@ -329,14 +363,17 @@ __PACKAGE__->register_method ({ local $SIG{'__WARN__'} = 'DEFAULT'; # do not fill up syslog - my ($ruleset, $ipset_ruleset, $rulesetv6) = PVE::Firewall::compile(undef, undef, undef, $param->{verbose}); + PVE::Firewall::set_verbose($param->{verbose}); + + my ($ruleset, $ipset_ruleset, $rulesetv6, $ebtables_ruleset) = PVE::Firewall::compile(); + + PVE::FirewallSimulator::debug(); - 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}; + print Dumper($ruleset->{filter}) if $param->{verbose}; + print Dumper($ruleset->{raw}) if $param->{verbose}; my $test = { from => $param->{from}, @@ -350,11 +387,11 @@ __PACKAGE__->register_method ({ if (!defined($test->{to})) { $test->{to} = 'host'; - PVE::FirewallSimulator::add_trace("Set Zone: to => '$test->{to}'\n"); - } + 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"); + PVE::FirewallSimulator::add_trace("Set Zone: from => '$test->{from}'\n"); } my $vmdata = PVE::Firewall::read_local_vm_config(); @@ -367,9 +404,9 @@ __PACKAGE__->register_method ({ $test->{action} = 'QUERY'; - my $res = PVE::FirewallSimulator::simulate_firewall($ruleset, $ipset_ruleset, - $host_ip, $vmdata, $test); - + my $res = PVE::FirewallSimulator::simulate_firewall( + $ruleset->{filter}, $ipset_ruleset, $host_ip, $vmdata, $test); + print "ACTION: $res\n"; return undef; @@ -385,7 +422,7 @@ our $cmddef = { status => [ __PACKAGE__, 'status', [], undef, sub { my $res = shift; my $status = ($res->{enable} ? "enabled" : "disabled") . '/' . $res->{status}; - + if ($res->{changes}) { print "Status: $status (pending changes)\n"; } else { @@ -395,19 +432,3 @@ our $cmddef = { }; 1; - -__END__ - -=head1 NAME - -pve-firewall - PVE Firewall Daemon - -=head1 SYNOPSIS - -=include synopsis - -=head1 DESCRIPTION - -This service updates iptables rules periodically. - -=include pve_copyright