X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=blobdiff_plain;f=src%2FPVE%2FFirewall.pm;h=76684f9676ddc5a49bfcd3ab61b4725d72e8bf75;hp=c5d216e5d90b1a6fc63de18eead54fd9bdffae16;hb=d31689ee39ce7365231f02301ec8770dfc633eb8;hpb=808d711d1cacee467f51e48e6e21618ff5d09541;ds=sidebyside diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index c5d216e..76684f9 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -489,7 +489,8 @@ my $pve_fw_parsed_macros; my $pve_fw_macro_descr; my $pve_fw_preferred_macro_names = {}; -my $pve_std_chains = { +my $pve_std_chains = {}; +$pve_std_chains->{4} = { 'PVEFW-SET-ACCEPT-MARK' => [ "-j MARK --set-mark 1", ], @@ -760,7 +761,10 @@ sub local_network { return $__local_network; } -my $max_iptables_ipset_name_length = 27; +# ipset names are limited to 31 characters, and we use '_swap' +# suffix for atomic update, for example PVEFW-${VMID}-${ipset_name}_swap + +my $max_iptables_ipset_name_length = 31 - length("_swap"); sub compute_ipset_chain_name { my ($vmid, $ipset_name) = @_; @@ -777,6 +781,12 @@ sub compute_ipset_chain_name { return "PVEFW-$id"; } +sub compute_ipfilter_ipset_name { + my ($iface) = @_; + + return "ipfilter-$iface"; +} + sub parse_address_list { my ($str) = @_; @@ -792,6 +802,8 @@ sub parse_address_list { my $count = 0; my $iprange = 0; + my $ipversion = undef; + foreach my $elem (split(/,/, $str)) { $count++; if (!Net::IP->new($elem)) { @@ -799,9 +811,11 @@ sub parse_address_list { die "invalid IP address: $err\n"; } $iprange = 1 if $elem =~ m/-/; + $ipversion = Net::IP::ip_get_version($elem); #fixme : don't work with range } die "you can use a range in a list\n" if $iprange && $count > 1; + return $ipversion; } sub parse_port_name_number_or_range { @@ -843,8 +857,8 @@ sub pve_fw_verify_port_spec { return $portstr; } -PVE::JSONSchema::register_format('pve-fw-v4addr-spec', \&pve_fw_verify_v4addr_spec); -sub pve_fw_verify_v4addr_spec { +PVE::JSONSchema::register_format('pve-fw-addr-spec', \&pve_fw_verify_addr_spec); +sub pve_fw_verify_addr_spec { my ($list) = @_; parse_address_list($list); @@ -943,11 +957,11 @@ my $rule_properties = { }, iface => get_standard_option('pve-iface', { optional => 1 }), source => { - type => 'string', format => 'pve-fw-v4addr-spec', + type => 'string', format => 'pve-fw-addr-spec', optional => 1, }, dest => { - type => 'string', format => 'pve-fw-v4addr-spec', + type => 'string', format => 'pve-fw-addr-spec', optional => 1, }, proto => { @@ -1061,6 +1075,7 @@ sub verify_rule { my ($rule, $cluster_conf, $fw_conf, $rule_env, $noerr) = @_; my $allow_groups = $rule_env eq 'group' ? 0 : 1; + my $ipversion = undef; my $allow_iface = $rule_env_iface_lookup->{$rule_env}; die "unknown rule_env '$rule_env'\n" if !defined($allow_iface); # should not happen @@ -1159,13 +1174,13 @@ sub verify_rule { } if ($rule->{source}) { - eval { parse_address_list($rule->{source}); }; + eval { $ipversion = parse_address_list($rule->{source}); }; &$add_error('source', $@) if $@; &$check_ipset_or_alias_property('source'); } if ($rule->{dest}) { - eval { parse_address_list($rule->{dest}); }; + eval { $ipversion = parse_address_list($rule->{dest}); }; &$add_error('dest', $@) if $@; &$check_ipset_or_alias_property('dest'); } @@ -1185,6 +1200,7 @@ sub verify_rule { } $rule->{errors} = $errors if $error_count; + $rule->{ipversion} = $ipversion if $ipversion; return $rule; } @@ -1205,6 +1221,46 @@ sub copy_rule_data { return $rule; } +sub rules_modify_permissions { + my ($rule_env) = @_; + + if ($rule_env eq 'host') { + return { + check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + }; + } elsif ($rule_env eq 'cluster' || $rule_env eq 'group') { + return { + check => ['perm', '/', [ 'Sys.Modify' ]], + }; + } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { + return { + check => ['perm', '/vms/{vmid}', [ 'VM.Config.Network' ]], + } + } + + return undef; +} + +sub rules_audit_permissions { + my ($rule_env) = @_; + + if ($rule_env eq 'host') { + return { + check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + }; + } elsif ($rule_env eq 'cluster' || $rule_env eq 'group') { + return { + check => ['perm', '/', [ 'Sys.Audit' ]], + }; + } elsif ($rule_env eq 'vm' || $rule_env eq 'ct') { + return { + check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], + } + } + + return undef; +} + # core functions my $bridge_firewall_enabled = 0; @@ -1746,10 +1802,7 @@ sub generate_venet_rules_direction { my $chain = "venet0-$vmid-$direction"; - my $ipfilter_ipset = compute_ipset_chain_name($vmid, 'ipfilter') - if $vmfw_conf->{ipset}->{ipfilter}; - - ruleset_create_vm_chain($ruleset, $chain, $options, undef, $ipfilter_ipset, $direction); + ruleset_create_vm_chain($ruleset, $chain, $options, undef, undef, $direction); ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $chain, 'venet', $direction); @@ -1791,27 +1844,34 @@ sub generate_tap_rules_direction { my $tapchain = "$iface-$direction"; - my $ipfilter_ipset = compute_ipset_chain_name($vmid, 'ipfilter') - if $vmfw_conf->{ipset}->{ipfilter}; + my $ipfilter_name = compute_ipfilter_ipset_name($netid); + my $ipfilter_ipset = compute_ipset_chain_name($vmid, $ipfilter_name) + if $vmfw_conf->{ipset}->{$ipfilter_name}; + # create chain with mac and ip filter ruleset_create_vm_chain($ruleset, $tapchain, $options, $macaddr, $ipfilter_ipset, $direction); - ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options); + if ($options->{enable}) { + ruleset_generate_vm_rules($ruleset, $rules, $cluster_conf, $vmfw_conf, $tapchain, $netid, $direction, $options); - ruleset_generate_vm_ipsrules($ruleset, $options, $direction, $iface); + ruleset_generate_vm_ipsrules($ruleset, $options, $direction, $iface); - # implement policy - my $policy; + # implement policy + my $policy; - if ($direction eq 'OUT') { - $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default - } else { + if ($direction eq 'OUT') { + $policy = $options->{policy_out} || 'ACCEPT'; # allow everything by default + } else { $policy = $options->{policy_in} || 'DROP'; # allow nothing by default - } + } - my $accept = generate_nfqueue($options); - my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept; - ruleset_add_chain_policy($ruleset, $tapchain, $vmid, $policy, $loglevel, $accept_action); + my $accept = generate_nfqueue($options); + my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : $accept; + ruleset_add_chain_policy($ruleset, $tapchain, $vmid, $policy, $loglevel, $accept_action); + } else { + my $accept_action = $direction eq 'OUT' ? "PVEFW-SET-ACCEPT-MARK" : 'ACCEPT'; + ruleset_add_chain_policy($ruleset, $tapchain, $vmid, 'ACCEPT', $loglevel, $accept_action); + } # plug the tap chain to bridge chain if ($direction eq 'IN') { @@ -2588,29 +2648,35 @@ sub get_option_log_level { } sub generate_std_chains { - my ($ruleset, $options) = @_; + my ($ruleset, $options, $ipversion) = @_; + + my $std_chains = $pve_std_chains->{$ipversion} || die "internal error"; my $loglevel = get_option_log_level($options, 'smurf_log_level'); - # same as shorewall smurflog. - my $chain = 'PVEFW-smurflog'; - $pve_std_chains->{$chain} = []; + my $chain; - push @{$pve_std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel; - push @{$pve_std_chains->{$chain}}, "-j DROP"; + if ($ipversion == 4) { + # same as shorewall smurflog. + $chain = 'PVEFW-smurflog'; + $std_chains->{$chain} = []; + + push @{$std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel; + push @{$std_chains->{$chain}}, "-j DROP"; + } # same as shorewall logflags action. $loglevel = get_option_log_level($options, 'tcp_flags_log_level'); $chain = 'PVEFW-logflags'; - $pve_std_chains->{$chain} = []; + $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"; + push @{$std_chains->{$chain}}, get_log_rule_base($chain, 0, "DROP: ", $loglevel) if $loglevel; + push @{$std_chains->{$chain}}, "-j DROP"; - foreach my $chain (keys %$pve_std_chains) { + foreach my $chain (keys %$std_chains) { ruleset_create_chain($ruleset, $chain); - foreach my $rule (@{$pve_std_chains->{$chain}}) { + foreach my $rule (@{$std_chains->{$chain}}) { if (ref($rule)) { ruleset_generate_rule($ruleset, $chain, $rule); } else { @@ -2783,6 +2849,13 @@ sub compile { $vmfw_configs = read_vm_firewall_configs($cluster_conf, $vmdata, undef, $verbose); } + my ($ruleset, $ipset_ruleset) = compile_iptables_filter($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, 4, $verbose); + return ($ruleset, $ipset_ruleset); +} + +sub compile_iptables_filter { + my ($cluster_conf, $hostfw_conf, $vmfw_configs, $vmdata, $ipversion, $verbose) = @_; + $cluster_conf->{ipset}->{venet0} = []; my $venet0_ipset_chain = compute_ipset_chain_name(0, 'venet0'); @@ -2830,7 +2903,7 @@ sub compile { ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o venet0 -m set --match-set ${venet0_ipset_chain} dst -j PVEFW-VENET-IN"); - generate_std_chains($ruleset, $hostfw_options); + generate_std_chains($ruleset, $hostfw_options, $ipversion); my $hostfw_enable = !(defined($hostfw_options->{enable}) && ($hostfw_options->{enable} == 0)); @@ -2849,7 +2922,6 @@ sub compile { my $conf = $vmdata->{qemu}->{$vmid}; my $vmfw_conf = $vmfw_configs->{$vmid}; return if !$vmfw_conf; - return if !$vmfw_conf->{options}->{enable}; generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf); @@ -2876,32 +2948,34 @@ sub compile { my $vmfw_conf = $vmfw_configs->{$vmid}; return if !$vmfw_conf; - return if !$vmfw_conf->{options}->{enable}; generate_ipset_chains($ipset_ruleset, $cluster_conf, $vmfw_conf); - if ($conf->{ip_address} && $conf->{ip_address}->{value}) { - my $ip = $conf->{ip_address}->{value}; - $ip =~ s/\s+/,/g; - parse_address_list($ip); # make sure we have a valid $ip list + if ($vmfw_conf->{options}->{enable}) { + if ($conf->{ip_address} && $conf->{ip_address}->{value}) { + my $ip = $conf->{ip_address}->{value}; + $ip =~ s/\s+/,/g; + parse_address_list($ip); # make sure we have a valid $ip list - my @ips = split(',', $ip); + my @ips = split(',', $ip); - foreach my $singleip (@ips) { - my $venet0ipset = {}; - $venet0ipset->{cidr} = $singleip; - push @{$cluster_conf->{ipset}->{venet0}}, $venet0ipset; - } + foreach my $singleip (@ips) { + my $venet0ipset = {}; + $venet0ipset->{cidr} = $singleip; + push @{$cluster_conf->{ipset}->{venet0}}, $venet0ipset; + } - generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'IN'); - generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'OUT'); + generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'IN'); + generate_venet_rules_direction($ruleset, $cluster_conf, $vmfw_conf, $vmid, $ip, 'OUT'); + } } if ($conf->{netif} && $conf->{netif}->{value}) { my $netif = PVE::OpenVZ::parse_netif($conf->{netif}->{value}); foreach my $netid (keys %$netif) { my $d = $netif->{$netid}; - + my $bridge = $d->{bridge}; + next if !$bridge || $bridge !~ m/^vmbr\d+(v(\d+))?f$/; # firewall enabled ? my $macaddr = $d->{mac}; my $iface = $d->{host_ifname}; generate_tap_rules_direction($ruleset, $cluster_conf, $iface, $netid, $macaddr,