X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2Fpve-firewall;h=e7a2337c4c9da59c19c4eda999b369303ef62a8c;hp=0c929794693d4e4be9373fcdb51cd7a6346cb6a7;hb=3bce273b66ebea7898c4cfe0af38c4278054c57c;hpb=8b453a09f302dd91db5c02c92da144df37503d79 diff --git a/src/pve-firewall b/src/pve-firewall index 0c92979..e7a2337 100755 --- a/src/pve-firewall +++ b/src/pve-firewall @@ -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? } @@ -305,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.", @@ -324,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; @@ -345,7 +363,7 @@ __PACKAGE__->register_method ({ __PACKAGE__->register_method ({ name => 'compile', path => 'compile', - method => 'POST', + method => 'GET', description => "Compile and print firewall rules. This is useful for testing.", parameters => { additionalProperties => 0, @@ -359,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); @@ -375,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"; } }], };