X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=dd6ec61a38e5d02a9dd7e695a4c023f97a8782eb;hp=aa3555a9786da965961064ee4103cd9f2d28a8cd;hb=35f0c37e4dfcc017ca3446971efcb5f78155a7e7;hpb=c71e785ff490a98c24ce4c8ed8bbebe07e045e47 diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index aa3555a..dd6ec61 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -56,12 +56,11 @@ PVE::JSONSchema::register_standard_option('ipset-name', { maxLength => 20, }); -PVE::JSONSchema::register_standard_option('pve-config-digest', { - description => "This digest/signature can be used to prevent updates when the original configuration was changed by somebody else.", - type => 'string', +PVE::JSONSchema::register_standard_option('pve-fw-loglevel' => { + description => "Log level.", + type => 'string', + enum => ['emerg', 'alert', 'crit', 'err', 'warning', 'notice', 'info', 'debug', 'nolog'], optional => 1, - maxLength => 27, - minLength => 27, }); my $security_group_pattern = '[A-Za-z][A-Za-z0-9\-\_]+'; @@ -792,6 +791,52 @@ sub pve_fw_verify_protocol_spec { # helper function for API +sub copy_opject_with_digest { + my ($object) = @_; + + my $sha = Digest::SHA->new('sha1'); + + my $res = {}; + foreach my $k (sort keys %$object) { + my $v = $object->{$k}; + next if !defined($v); + $res->{$k} = $v; + $sha->add($k, ':', $v, "\n"); + } + + my $digest = $sha->b64digest; + + $res->{digest} = $digest; + + return wantarray ? ($res, $digest) : $res; +} + +sub copy_list_with_digest { + my ($list) = @_; + + my $sha = Digest::SHA->new('sha1'); + + my $res = []; + foreach my $entry (@$list) { + my $data = {}; + foreach my $k (sort keys %$entry) { + my $v = $entry->{$k}; + next if !defined($v); + $data->{$k} = $v; + $sha->add($k, ':', $v, "\n"); + } + push @$res, $data; + } + + my $digest = $sha->b64digest; + + foreach my $entry (@$res) { + $entry->{digest} = $digest; + } + + return wantarray ? ($res, $digest) : $res; +} + my $rule_properties = { pos => { description => "Update rule at position .", @@ -849,23 +894,6 @@ my $rule_properties = { }, }; -sub cleanup_fw_rule { - my ($rule, $digest, $pos) = @_; - - my $r = {}; - - foreach my $k (keys %$rule) { - next if !$rule_properties->{$k}; - my $v = $rule->{$k}; - next if !defined($v); - $r->{$k} = $v; - $r->{digest} = $digest; - $r->{pos} = $pos; - } - - return $r; -} - sub add_rule_properties { my ($properties) = @_; @@ -1197,7 +1225,7 @@ sub ruleset_generate_cmdstr { push @cmd, "-m iprange --dst-range $dest"; } else { - push @cmd, "-s $dest"; + push @cmd, "-d $dest"; } } @@ -1482,7 +1510,7 @@ sub generate_nfqueue { $action .= " --queue-num $1"; } } - $action .= " --queue-bypass"; + $action .= " --queue-bypass" if $feature_ipset_nomatch; #need kernel 3.10 }else{ $action = "ACCEPT"; } @@ -1611,7 +1639,9 @@ sub enable_host_firewall { # fixme: allow security groups my $options = $hostfw_conf->{options}; + my $cluster_options = $cluster_conf->{options}; my $rules = $hostfw_conf->{rules}; + my $cluster_rules = $cluster_conf->{rules}; # host inbound firewall my $chain = "PVEFW-HOST-IN"; @@ -1637,13 +1667,14 @@ sub enable_host_firewall { # we use RETURN because we need to check also tap rules my $accept_action = 'RETURN'; - foreach my $rule (@$rules) { + # add host rules first, so that cluster wide rules can be overwritten + foreach my $rule (@$rules, @$cluster_rules) { next if $rule->{type} ne 'in'; ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf); } # implement input policy - my $policy = $options->{policy_in} || 'DROP'; # allow nothing by default + my $policy = $cluster_options->{policy_in} || 'DROP'; # allow nothing by default ruleset_add_chain_policy($ruleset, $chain, 0, $policy, $loglevel, $accept_action); # host outbound firewall @@ -1662,13 +1693,14 @@ sub enable_host_firewall { # we use RETURN because we may want to check other thigs later $accept_action = 'RETURN'; - foreach my $rule (@$rules) { + # add host rules first, so that cluster wide rules can be overwritten + foreach my $rule (@$rules, @$cluster_rules) { next if $rule->{type} ne 'out'; ruleset_generate_rule($ruleset, $chain, $rule, { ACCEPT => $accept_action, REJECT => "PVEFW-reject" }, undef, $cluster_conf); } # implement output policy - $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default + $policy = $cluster_options->{policy_out} || 'ACCEPT'; # allow everything by default ruleset_add_chain_policy($ruleset, $chain, 0, $policy, $loglevel, $accept_action); ruleset_addrule($ruleset, "PVEFW-OUTPUT", "-j PVEFW-HOST-OUT"); @@ -1829,16 +1861,13 @@ sub parse_hostfw_option { my $loglevels = "emerg|alert|crit|err|warning|notice|info|debug|nolog"; - if ($line =~ m/^(enable|dhcp|nosmurfs|tcpflags|allow_bridge_route|optimize):\s*(0|1)\s*$/i) { + if ($line =~ m/^(enable|nosmurfs|tcpflags|allow_bridge_route|optimize):\s*(0|1)\s*$/i) { $opt = lc($1); $value = int($2); } elsif ($line =~ m/^(log_level_in|log_level_out|tcp_flags_log_level|smurf_log_level):\s*(($loglevels)\s*)?$/i) { $opt = lc($1); $value = $2 ? lc($3) : ''; - } elsif ($line =~ m/^(policy_(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) { - $opt = lc($1); - $value = uc($3); - } elsif ($line =~ m/^(nf_conntrack_max):\s*(\d+)\s*$/i) { + } elsif ($line =~ m/^(nf_conntrack_max|nf_conntrack_tcp_timeout_established):\s*(\d+)\s*$/i) { $opt = lc($1); $value = int($2); } else { @@ -1857,6 +1886,9 @@ sub parse_clusterfw_option { if ($line =~ m/^(enable):\s*(0|1)\s*$/i) { $opt = lc($1); $value = int($2); + } elsif ($line =~ m/^(policy_(in|out)):\s*(ACCEPT|DROP|REJECT)\s*$/i) { + $opt = lc($1); + $value = uc($3); } else { chomp $line; die "can't parse option '$line'\n" @@ -1872,11 +1904,7 @@ sub parse_vm_fw_rules { my $section; - my $digest = Digest::SHA->new('sha1'); - while (defined(my $line = <$fh>)) { - $digest->add($line); - next if $line =~ m/^#/; next if $line =~ m/^\s*$/; @@ -1914,8 +1942,6 @@ sub parse_vm_fw_rules { push @{$res->{$section}}, $rule; } - $res->{digest} = $digest->b64digest; - return $res; } @@ -1926,11 +1952,7 @@ sub parse_host_fw_rules { my $section; - my $digest = Digest::SHA->new('sha1'); - while (defined(my $line = <$fh>)) { - $digest->add($line); - next if $line =~ m/^#/; next if $line =~ m/^\s*$/; @@ -1968,8 +1990,6 @@ sub parse_host_fw_rules { push @{$res->{$section}}, $rule; } -$res->{digest} = $digest->b64digest; - return $res; } @@ -1984,14 +2004,11 @@ sub parse_cluster_fw_rules { options => {}, groups => {}, group_comments => {}, - ipset => {} + ipset => {} , + ipset_comments => {}, }; - my $digest = Digest::SHA->new('sha1'); - while (defined(my $line = <$fh>)) { - $digest->add($line); - next if $line =~ m/^#/; next if $line =~ m/^\s*$/; @@ -2008,7 +2025,8 @@ sub parse_cluster_fw_rules { $group = lc($1); my $comment = $2; $res->{$section}->{$group} = []; - $res->{group_comments}->{$group} = $comment if $comment; + $res->{group_comments}->{$group} = decode('utf8', $comment) + if $comment; next; } @@ -2017,10 +2035,13 @@ sub parse_cluster_fw_rules { next; } - if ($line =~ m/^\[ipset\s+(\S+)\]\s*$/i) { + if ($line =~ m/^\[ipset\s+(\S+)\]\s*(?:#\s*(.*?)\s*)?$/i) { $section = 'ipset'; $group = lc($1); + my $comment = $2; $res->{$section}->{$group} = []; + $res->{ipset_comments}->{$group} = decode('utf8', $comment) + if $comment; next; } @@ -2075,7 +2096,6 @@ sub parse_cluster_fw_rules { } } - $res->{digest} = $digest->b64digest; return $res; } @@ -2267,6 +2287,7 @@ sub generate_std_chains { # same as shorewall smurflog. my $chain = 'PVEFW-smurflog'; + $pve_std_chains->{$chain} = []; push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel; push @{$pve_std_chains->{$chain}}, "-j DROP"; @@ -2274,6 +2295,8 @@ sub generate_std_chains { # same as shorewall logflags action. $loglevel = get_option_log_level($options, 'tcp_flags_log_level'); $chain = 'PVEFW-logflags'; + $pve_std_chains->{$chain} = []; + # fixme: is this correctly logged by pvewf-logger? (ther is no --log-ip-options for NFLOG) push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel; push @{$pve_std_chains->{$chain}}, "-j DROP"; @@ -2411,7 +2434,12 @@ sub save_clusterfw_conf { $raw .= &$format_options($options) if scalar(keys %$options); foreach my $ipset (sort keys %{$cluster_conf->{ipset}}) { - $raw .= "[IPSET $ipset]\n\n"; + if (my $comment = $cluster_conf->{ipset_comments}->{$ipset}) { + my $utf8comment = encode('utf8', $comment); + $raw .= "[IPSET $ipset] # $utf8comment\n\n"; + } else { + $raw .= "[IPSET $ipset]\n\n"; + } my $options = $cluster_conf->{ipset}->{$ipset}; $raw .= &$format_ipset($options); $raw .= "\n"; @@ -2427,7 +2455,8 @@ sub save_clusterfw_conf { foreach my $group (sort keys %{$cluster_conf->{groups}}) { my $rules = $cluster_conf->{groups}->{$group}; if (my $comment = $cluster_conf->{group_comments}->{$group}) { - $raw .= "[group $group] # $comment\n\n"; + my $utf8comment = encode('utf8', $comment); + $raw .= "[group $group] # $utf8comment\n\n"; } else { $raw .= "[group $group]\n\n"; } @@ -2467,13 +2496,16 @@ sub save_hostfw_conf { } sub compile { + my ($cluster_conf, $hostfw_conf) = @_; + + $cluster_conf = load_clusterfw_conf() if !$cluster_conf; + $hostfw_conf = load_hostfw_conf() if !$hostfw_conf; + my $vmdata = read_local_vm_config(); my $vmfw_configs = read_vm_firewall_configs($vmdata); my $routing_table = read_proc_net_route(); - my $cluster_conf = load_clusterfw_conf(); - my $ipset_ruleset = {}; generate_ipset_chains($ipset_ruleset, $cluster_conf); @@ -2484,7 +2516,6 @@ sub compile { ruleset_create_chain($ruleset, "PVEFW-FORWARD"); - my $hostfw_conf = load_hostfw_conf(); my $hostfw_options = $hostfw_conf->{options} || {}; generate_std_chains($ruleset, $hostfw_options); @@ -2580,7 +2611,7 @@ sub compile { ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j DROP"); ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j DROP"); - return wantarray ? ($ruleset, $hostfw_conf, $ipset_ruleset) : $ruleset; + return ($ruleset, $ipset_ruleset); } sub get_ruleset_status { @@ -2749,6 +2780,8 @@ sub apply_ruleset { update_nf_conntrack_max($hostfw_conf); + update_nf_conntrack_tcp_timeout_established($hostfw_conf); + my ($ipset_create_cmdlist, $ipset_delete_cmdlist, $ipset_changes) = get_ipset_cmdlist($ipset_ruleset, undef, $verbose); @@ -2813,6 +2846,16 @@ sub update_nf_conntrack_max { } } +sub update_nf_conntrack_tcp_timeout_established { + my ($hostfw_conf) = @_; + + my $options = $hostfw_conf->{options} || {}; + + my $value = defined($options->{nf_conntrack_tcp_timeout_established}) ? $options->{nf_conntrack_tcp_timeout_established} : 432000; + + PVE::ProcFSTools::write_proc_entry("/proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established", $value); +} + sub remove_pvefw_chains { my ($chash, $hooks) = iptables_get_chains(); @@ -2840,9 +2883,25 @@ sub update { my ($start, $verbose) = @_; my $code = sub { + + my $cluster_conf = load_clusterfw_conf(); + my $cluster_options = $cluster_conf->{options}; + + my $enable = $cluster_options->{enable}; + my $status = read_pvefw_status(); - my ($ruleset, $hostfw_conf, $ipset_ruleset) = compile(); + die "Firewall is disabled - cannot start\n" if !$enable && $start; + + if (!$enable) { + PVE::Firewall::remove_pvefw_chains(); + print "Firewall disabled\n" if $verbose; + return; + } + + my $hostfw_conf = load_hostfw_conf(); + + my ($ruleset, $ipset_ruleset) = compile($cluster_conf, $hostfw_conf); if ($start || $status eq 'active') {